Join关联查询优化及原理

采用左外连接

下面开始 EXPLAIN 分析

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

在这里插入图片描述
结论:type 有All
添加索引优化

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

在这里插入图片描述
可以看到第二行的 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;

在这里插入图片描述

采用内连接

(这里的实验数据中,student_info数据有100万条,course中有100条)
不添加索引的情况

explain select * from student_info a inner join course b
on a.course_id =b.course_id ;

在这里插入图片描述
给course添加索引,此时a作为驱动表,b作为被驱动表

create index x on course(course_id);
explain select * from student_info a inner join course b
on a.course_id =b.course_id ;

在这里插入图片描述
给student_info也添加索引

此时有两个索引,查询优化器把course当做驱动表,student_info当做被驱动表,因为查询优化器发现studet_info数据更多,使用索引的话会更节约成本,所以选择小表当做驱动表

create index y on student_info(course_id);
explain select * from student_info a inner join course b
on a.course_id =b.course_id ;

在这里插入图片描述
现在删除全部索引,创建student_info的索引,发现查询优化器把course作为了驱动表,student_info作为了被驱动表

因为查询优化器发现student_info是可以使用索引的,所以就把它当做了被驱动表,这样能更快查询

drop index x on course;
drop index y on student_info;
create index y on student_info(course_id);
explain select * from student_info a inner join course b
on a.course_id =b.course_id ;

在这里插入图片描述

总结:

  • 无论如何,查询优化器选择的驱动表都会全表扫描;有两个索引时,查询优化器会选择小表来当做驱动表。
  • 而前面的外连接,查询优化器不能改变驱动表的顺序

join语句原理

  • 如果两个都没有索引,那么小表驱动大表
  • 如果一个有索引,那么这个一定作为被驱动表
  • 如果两个都有索引,那么还是小表驱动大表

left join左表一定是驱动表吗?

插入数据:

mysql> create table a(f1 int, f2 int, index(f1))engine=innodb;
Query OK, 0 rows affected (0.01 sec)

mysql> create table b(f1 int, f2 int)engine=innodb;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into a values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6);
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> insert into b values(3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
Query OK, 6 rows affected (0.02 sec)
Records: 6  Duplicates: 0  Warnings: 0

执行下列语句,发现b作为了驱动表:

mysql> explain select * from a left join b on(a.f1=b.f1) where (a.f2=b.f2);
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref       | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL      |    6 |   100.00 | Using where |
|  1 | SIMPLE      | a     | NULL       | ref  | f1            | f1   | 5       | test.b.f1 |    1 |    16.67 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

我们使用show warnings,查看优化器优化的语句,发现它把左连接变成了内连接:

mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                   |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `test`.`a`.`f1` AS `f1`,`test`.`a`.`f2` AS `f2`,`test`.`b`.`f1` AS `f1`,`test`.`b`.`f2` AS `f2` from `test`.`a` join `test`.`b` where ((`test`.`a`.`f1` = `test`.`b`.`f1`) and (`test`.`a`.`f2` = `test`.`b`.`f2`)) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

这个例子说明了两点
1、即使我们在SQL语句中写成left join,执行过程还是有可能不是从左到右连接的。也就是说,使用left join时,左边的表不一定是驱动表。
2、如果需要left join的语义,就不能把被驱动表的字段放在where条件里面做等值判断或不等值判断,必须都写在on里面。

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

Join关联查询优化及原理_第1张图片
可以看到这种方式效率是非常低的,以上述表A数据100条,表B数据1000条计算,则A*B= 10万次。开销统计如下:
Join关联查询优化及原理_第2张图片
这里从驱动表中拿出一条数据加载到内存中,然后把被驱动表全部加载到内存中,比对完后清掉,继续把驱动表中加载一条数据到内存中,此时的 I/O次数是很高的

当然mysql肯定不会这么粗暴的去进行表的连接,所以就出现了后面的两种对Nested-Loop Join 优化算法。

索引嵌套循环连接

Index Nested-Loop Join其优化的思路主要是为了减少内层表数据的匹配次数,所以要求被驱动表上必须有索引才行。通过外层表匹配条件直接与内层表索引进行匹配,避免和内层表的每条记录去进行比较,这样极大的减少了对内层表的匹配次数。
Join关联查询优化及原理_第3张图片
驱动表中的每条记录通过被驱动表的索引进行访问,因为索引查询的成本是比较固定的,故mysql优化器都倾向于使用记录数少的表作为驱动表(外表)。
Join关联查询优化及原理_第4张图片

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

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

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

注意:

  • 这里缓存的不只是关联表的列,select后面的列也会缓存起来
    在一个有N个join关联的sql中会分配N-1个join buffer。所以查询的时候尽量减少不必要的字段,可以让join buffer中可以存放更多的列。

Join关联查询优化及原理_第5张图片
Join关联查询优化及原理_第6张图片

Join小结

  1. 整体效率比较:INLJ>BNLJ>SNLJ
  2. 永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)(小的度量单位是指表行数*每行大小)
    3.
  3. 为被驱动表匹配的条件增加索引(减少内层表的循环匹配次数)
  4. 增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少)
  5. 减少驱动表不必要的字段查询(字段越少,join buffer所缓存的数据就越多)

Hash Join

从MySQL的8.0.20版本开始就废弃了 BNLJ,默认都会使用hash join

  • Nested Loop:
    对于被连接的数据子集较小的情况,Nested Loop是个较好的选择。
  • Hash Join是做大数据集连接时的常用方式,优化器使用两个表中较小(相对较小)的表利用Join Key在内存中建立散列表,然后扫描较大的表并探测散列表,找出与Hash表匹配的行
    1)这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。
    2)在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段从而尽量提高I/o的性能。
    3)它能够很好的工作于没有索引的大表和并行查询的环境中,并提供最好的性能。大多数人都说它是Join的重型升降机。Hash Join只能应用于等值连接(如WHERE A.COL1=B.COL2),这是由Hash的特点决定的。

Join关联查询优化及原理_第7张图片

你可能感兴趣的:(Mysql,mysql)