前面我们我们介绍了索引的优化,这篇文章我们介绍一下关联查询的优化。

我们首先准备一下数据库表,然后各插入20条数据

MySQL关联查询优化 mysql表关联查询优化_database

采用左外连接

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

MySQL关联查询优化 mysql表关联查询优化_数据库_02

 我们可以看到上面的type字段为all即为全表扫描。


添加索引优化

ALTER TABLE book ADD INDEX Y ( card); #【被驱动表】,可以避免全表扫描 
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

MySQL关联查询优化 mysql表关联查询优化_MySQL关联查询优化_03



可以看到第二行的 type 变为了 ref , rows 也变成了优化比较明显。这是由左连接特性决定的。 LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以 右边是我们的关键点,一定需要建立索引 。



ALTER TABLE `type` ADD INDEX X (card); #【驱动表】,无法避免全表扫描 
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

MySQL关联查询优化 mysql表关联查询优化_database_04

 紧接着我们做如下操作

DROP INDEX Y ON book; 
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

MySQL关联查询优化 mysql表关联查询优化_数据库_05

所以,我们一定要在被驱动表上加上索引。

采用内连接

drop index X on type; 
drop index Y on book;(如果已经删除了可以不用再执行该操作)



换成 inner join ( MySQL 自动选择驱动表)



EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

MySQL关联查询优化 mysql表关联查询优化_数据库_06

添加索引优化

ALTER TABLE book ADD INDEX Y ( card); 
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

 

MySQL关联查询优化 mysql表关联查询优化_服务器_07

 

ALTER TABLE type ADD INDEX X (card); 
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;

MySQL关联查询优化 mysql表关联查询优化_database_08

DROP INDEX X ON `type`; 
EXPLAIN SELECT SQL_NO_CACHE * FROM TYPE INNER JOIN book ON type.card=book.card;

 

MySQL关联查询优化 mysql表关联查询优化_MySQL关联查询优化_09

 



 

ALTER TABLE `type` ADD INDEX X (card);
 EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card=book.card;

MySQL关联查询优化 mysql表关联查询优化_数据库_10

 




join 语句原理



join方式连接多个表,本质就是各个表之间数据的循环匹配。mysql5.5版本之前,mysql只支持一种表间关联方式,就是嵌套循环。如果关联表中的数据量很大,则join关联的执行时间会非常长。在mysql5.5以后的版本中,mysql通过BNLJ算法来优化嵌套执行。




1.驱动表和被驱动表



驱动表就是主表,被驱动表就是从表、非驱动表




①对于内连接来说



select * from A join B on...



A一定是驱动表吗?不一定,优化器会根据你查询语句做优化,决定先查询哪张表,先查询的那张表就是驱动表,反之就是被驱动表。通过explain关键字可以查看。




②对于外连接来说




select * from A left join B  on...



或select * from B right A  on...



通常,大家会认为A就是驱动表,B就是被驱动表,但也未必。测试如下:




MySQL关联查询优化 mysql表关联查询优化_database_11


 

2.Simple Nested-Loop Join(简单嵌套循环连接)

算法相当简单,从表A中取出一条数据1,遍历表B,将匹配到的数据放到result。以此类推,驱动表A中的每一条记录与被驱动表B的记录进行判断


MySQL关联查询优化 mysql表关联查询优化_数据库_12


 


MySQL关联查询优化 mysql表关联查询优化_数据库_13


 3.Index Nested-Loop Join(索引嵌套循环)

其优化的思路主要是为了减少内层表数据的匹配次数,所以要求被驱动表上必须有索引才行。通过外层表匹配条件直接与内存表索引进行匹配,避免和内层表的每条记录去进行比较,这样极大的减少了对内层表的匹配次数。

MySQL关联查询优化 mysql表关联查询优化_服务器_14

 驱动表中的每条记录通过被驱动表的索引进行访问,因为索引查询的成本比较固定,故mysql优化器倾向于使用记录数少的表作为驱动表。

如果被驱动表加上索引,效率是非常高的,但如果索引不是主键索引,所以还得进行一次回表查询。相比,被驱动表的索引是主键索引,效率会更高。


两个结论:


1. 使用 join 语句,性能比强行拆成多个单表执行 SQL 语句的性能要好;


2. 如果使用 join 语句的话,需要让小表做驱动表。


4.Block Nested-Loop Join(块嵌套循环连接)


如果存在索引,那么会使用index的方式进行join,如果join的列没有索引,被驱动表要扫描的次数太多了。每次访问被驱动表,其表中的记录都会被加载到内存中,然后再从驱动表中取出一条与其匹配,匹配结束后清楚内存,然后再从驱动表中加载一条记录,然后把被驱动表的记录加载到内存匹配,这样周而复始,大大增加了IO的次数。为了减少被驱动表IO次数,就出现了BNLJ。


不再是逐条获取驱动表的数据,而是一块一块的获取,引入join buffer缓冲区, 将驱动表join 相关的部分数据列(大小受join buffer的限制)缓存到join buffer中,然后全表扫描被驱动表,被驱动表的每一条记录一次性和join buffer中所有的驱动表记录进行匹配(内存中操作)。将简单嵌套循环中的多次比较合并成一次,降低了备驱动表的访问频率。


注意:


这里缓存的不只是关联表的列,select后面的列也会缓存起来。


在一个有N个join关联的sql中会分配N-1个join buffer。所以查询的时候尽量减少不必要的字段,可以让join buffer中可以存放更多的列。


MySQL关联查询优化 mysql表关联查询优化_数据库_15


 

join_buffer_size的最大值在32为系统中可以申请4G,在64为操作系统中可以申请大于4G的空间。

5.小结

①整体效率比较 INLJ>BNLJ>SNLJ

②永远用小结果集驱动大结果集,其本质就是减少外层循环的数据量。小的度量单位指的是表行数*每行大小。

MySQL关联查询优化 mysql表关联查询优化_服务器_16

 ③为被驱动表匹配的条件增加索引,减少内存循环匹配次数

④增大join buffer 大小,一次性缓存数据越多,那么内层包的扫表次数就越少。

⑤减少驱动表不必要的字段查询,字段越少,join buffer缓存的数据越多

6.Hash Join

从mysql的8.0.20版本开始就废弃了BNLJ,因为从0.18版本就开始加入了hash join默认都会使用hash join。

①Nested Loop:对于被连接数据子集较小的情况下,是个比较好的选择。

②hash join 是做大数据集连接时的常用方式,优化器使用两个表中较小(相对较小)的表利用join key在内存中建立散列表,然后扫描较大的表并探测散列表,找出与hash表匹配的行。

Ⅰ 这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。

Ⅱ在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干个不同分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段从而尽量提高IOS的性能。

Ⅲ它能够很好的工作于没有索引的大表和并行查询环境中,并提供最好的性能。大多数都说他是join的重型升降机。hash join只能应用于等值连接,这是由hash的特点决定的。

MySQL关联查询优化 mysql表关联查询优化_数据库_17