采用左外连接
下面开始 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 条件用于确定如何从右表搜索行,左边一定都有,所以
右边是我们的关键点,一定需要建立索引
。 也就是left join 右边所关联的表的关联字段一定要建立索引。
只是对左边的表建立索引的话,是没有效果的,可以通过rows这一列看到,type表要读取的记录仍然是20条。
ALTER TABLE `type` ADD INDEX X (card); #【驱动表】,无法避免全表扫描
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
把右边的表的索引删除,可以发现现在又要走全表扫描了
DROP INDEX Y ON book;
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
采用内连接
删除先前的索引
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;
添加索引优化
向book表添加索引后,book自动成为被驱动表,提高了查询效率。
ALTER TABLE book ADD INDEX Y (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
如果新增了type表的索引,此时两个表都有索引,优化器会选择小数据量的表作为驱动表,用来驱动大表。
ALTER TABLE type ADD INDEX X (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM type INNER JOIN book ON type.card=book.card;
对于内连接来说,查询优化器可以决定谁作为驱动表,谁作为被驱动表出现的,接下来把type表的索引删了。可以看到有索引的book表又作为了被驱动表
DROP INDEX X ON `type`;
EXPLAIN SELECT SQL_NO_CACHE * FROM TYPE INNER JOIN book ON type.card=book.card;
向type表里面添加索引,此时又变成了被驱动表了
ALTER TABLE `type` ADD INDEX X (card);
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card=book.card;
join语句原理
join方式连接多个表,本质就是各个表之间数据的循环匹配。MySQL5.5版本之前,MySQL只支持一种表间关联方式,就是嵌套循环(Nested Loop Join)。如果关联表的数据量很大,则join关联的执行时间会很长。在MySQL5.5以后的版本中,MySQL通过引入BNLJ算法来优化嵌套执行。
驱动表和被驱动表
驱动表就是主表,被驱动表就是从表、非驱动表。
- 对于内连接来说:
A一定是驱动表吗?不一定,优化器会根据你查询语句做优化,决定先查哪张表。先查询的那张表就是驱动表,反之就是被驱动表。通过explain关键字可以查看。
SELECT * FROM A JOIN B ON ...
- 对于外连接来说:
SELECT * FROM A LEFT JOIN B ON ...
# 或
SELECT * FROM B RIGHT JOIN A ON ...
Simple Nested-Loop Join (简单嵌套循环连接)
算法相当简单,从表A中取出一条数据1,遍历表B,将匹配到的数据放到result.. 以此类推,驱动表A中的每一条记录与被驱动表B的记录进行判断:
可以看到这种方式效率是非常低的,以上述表A数据100条,表B数据1000条计算,则A*B=10万次。开销统计如下。当然mysql肯定不会这么粗暴的去进行表的连接,所以就出现了后面的两种对Nested-Loop Join优化算法。
Index Nested-Loop Join (索引嵌套循环连接)
Index Nested-Loop Join其优化的思路主要是为了
减少内存表数据的匹配次数
,所以要求被驱动表上必须有索引
才行。通过外层表匹配条件直接与内层表索引进行匹配,避免和内存表的每条记录去进行比较,这样极大的减少了对内存表的匹配次数。就是利用索引来提高匹配效率
驱动表中的每条记录通过被驱动表的索引进行访问,因为索引查询的成本是比较固定的,故mysql优化器都倾向于使用记录数少的表作为驱动表(外表)。如果被驱动表加索引,效率是非常高的,但如果索引不是主键索引,所以还得进行一次回表查询。相比,被驱动表的索引是主键索引,效率会更高。
Block Nested-Loop Join(块嵌套循环连接)
之前是将驱动表逐条与非驱动表的记录进行匹配,现在是引入join buffer缓冲区,将驱动表的记录缓冲到缓冲区,然后进行批量匹配,而不是逐条匹配。
Join小结
1、整体效率比较:INLJ > BNLJ > SNLJ
2、永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)(小的度量单位指的是表行数 * 每行大小)
select t1.b,t2.* from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=100; # 推荐 select t1.b,t2.* from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=100; # 不推荐
3、为被驱动表匹配的条件增加索引(减少内存表的循环匹配次数)
4、增大join buffer size的大小(一次索引的数据越多,那么内层包的扫描次数就越少)
5、减少驱动表不必要的字段查询(字段越少,join buffer所缓存的数据就越多)
Hash Join
从MySQL的8.0.20版本开始将废弃BNLJ,因为从MySQL8.0.18版本开始就加入了hash join默认都会使用hash join
Nested Loop:
对于被连接的数据子集较小的情况,Nested Loop是个较好的选择。
Hash Join是做
大数据集连接
时的常用方式,优化器使用两个表中较小(相对较小)的表利用Join Key在内存中建立散列表
,然后扫描较大的表并探测散列表,找出与Hash表匹配的行。
这种方式适合于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和。
在表很大的情况下并不能完全放入内存,这时优化器会将它分割成
若干不同的分区
,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段从而尽量提高I/O的性能。它能够很好的工作于没有索引的大表和并行查询的环境中,并提供最好的性能。大多数人都说它是Join的重型升降机。Hash Join只能应用于等值连接(如WHERE A.COL1 = B.COL2),这是由Hash的特点决定的。
小结
保证被驱动表的JOIN字段已经创建了索引
需要JOIN 的字段,数据类型保持绝对一致。
LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数。
INNER JOIN 时,MySQL会自动将 小结果集的表选为驱动表 。选择相信MySQL优化策略。
能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)
不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询。
衍生表建不了索引