MySQL JOIN原理

最近被公司某一开发问道JOIN了MySQL JOIN的问题,细数之下发下我对MySQL JOIN的理解并非很深入,因此也查看了不少文档,最后在InsideMySQL公众号看到了两篇关于JOIN的分析,感受写的太好了,拿出来分享一下我对于JOIN的实际测试吧。下面先介绍一下MySQL关于JOIN的算法,总共分为三种(来源为InsideMySQL):算法

MySQL是只支持一种JOIN算法Nested-Loop Join(嵌套循环连接),不像其余商业数据库能够支持哈希连接和合并链接,不过MySQL的Nested-Loop Join(嵌套循环连接)也是有不少变种,可以帮助MySQL更高效的执行JOIN操做:数据库

(1)Simple Nested-Loop Join(图片为InsideMySQL取来)缓存

这个算法相对来讲就是很简单了,从驱动表中取出R1匹配S表全部列,而后R2,R3,直到将R表中的全部数据匹配完,而后合并数据,能够看到这种算法要对S表进行RN次访问,虽然简单,可是相对来讲开销仍是太大了ide

(2)Index Nested-Loop Join,实现方式以下图:oop

索引嵌套连接因为非驱动表上有索引,因此比较的时候再也不须要一条条记录进行比较,而能够经过索引来减小比较,从而加速查询。这也就是平时咱们在作关联查询的时候必需要求关联字段有索引的一个主要缘由。性能

这种算法在连接查询的时候,驱动表会根据关联字段的索引进行查找,当在索引上找到了符合的值,再回表进行查询,也就是只有当匹配到索引之后才会进行回表。至于驱动表的选择,MySQL优化器通常状况下是会选择记录数少的做为驱动表,可是当SQL特别复杂的时候不排除会出现错误选择。测试

在索引嵌套连接的方式下,若是非驱动表的关联键是主键的话,这样来讲性能就会很是的高,若是不是主键的话,关联起来若是返回的行数不少的话,效率就会特别的低,由于要屡次的回表操做。先关联索引,而后根据二级索引的主键ID进行回表的操做。这样来讲的话性能相对就会不好。优化

(3)Block Nested-Loop Join,实现以下:3d

在有索引的状况下,MySQL会尝试去使用Index Nested-Loop Join算法,在有些状况下,可能Join的列就是没有索引,那么这时MySQL的选择绝对不会是最早介绍的Simple Nested-Loop Join算法,而是会优先使用Block Nested-Loop Join的算法。blog

Block Nested-Loop Join对比Simple Nested-Loop Join多了一个中间处理的过程,也就是join buffer,使用join buffer将驱动表的查询JOIN相关列都给缓冲到了JOIN BUFFER当中,而后批量与非驱动表进行比较,这也来实现的话,能够将屡次比较合并到一次,下降了非驱动表的访问频率。也就是只须要访问一次S表。这样来讲的话,就不会出现屡次访问非驱动表的状况了,也只有这种状况下才会访问join buffer。

在MySQL当中,咱们能够经过参数join_buffer_size来设置join buffer的值,而后再进行操做。默认状况下join_buffer_size=256K,在查找的时候MySQL会将全部的须要的列缓存到join buffer当中,包括select的列,而不是仅仅只缓存关联列。在一个有N个JOIN关联的SQL当中会在执行时候分配N-1个join buffer。

上面介绍完了,下面看一下具体的列子

(1)全表JOIN

EXPLAIN SELECT * FROM comments gc
JOIN comments_for gcf ON gc.comments_id=gcf.comments_id;

 

看一下输出信息:

 

能够看到在全表扫描的时候comments_for 做为了驱动表,此事由于关联字段是有索引的,因此对索引idx_commentsid进行了一个全索引扫描去匹配非驱动表comments ,每次可以匹配到一行。此时使用的就是Index Nested-Loop Join,经过索引进行了全表的匹配,咱们能够看到由于comments_for 表的量级远小于comments ,因此说MySQL优先选择了小表comments_for 做为了驱动表。

(2)全表JOIN+筛选条件

SELECT * FROM comments gc
JOIN comments_for gcf ON gc.comments_id=gcf.comments_id
WHERE gc.comments_id =2056

 

此时使用的是Index Nested-Loop Join,先对驱动表comments 的主键进行筛选,符合一条,对非驱动表comments_for 的索引idx_commentsid进行seek匹配,最终匹配结果预计为影响一条,这样就是仅仅对非驱动表的idx_commentsid索引进行了一次访问操做,效率相对来讲仍是很是高的。

(3)看一下关联字段是没有索引的状况:

EXPLAIN SELECT * FROM comments gc
JOIN comments_for gcf ON gc.order_id=gcf.product_id

 

咱们看一下执行计划:

从执行计划咱们就能够看出,这个表JOIN就是使用了Block Nested-Loop Join来进行表关联,先把comments_for (只有57行)这个小表做为驱动表,而后将comments_for 的须要的数据缓存到JOIN buffer当中,批量对comments 表进行扫描,也就是只进行一次匹配,前提是join buffer足够大可以存下comments_for的缓存数据。

并且咱们看到执行计划当中已经很明确的提示:Using where; Using join buffer (Block Nested Loop)

通常状况出现这种状况就证实咱们的SQL须要优化了。

要注意的是这种状况下,MySQL也会选择Simple Nested-Loop Join这种暴力的方法,我还没搞懂他这个优化器是怎么选择的,可是通常是使用Block Nested-Loop Join,由于CBO是基于开销的,Block Nested-Loop Join的性能相对于Simple Nested-Loop Join是要好不少的。

(4)看一下left join

EXPLAIN SELECT * FROM comments gc
LEFT JOIN comments_for gcf ON gc.comments_id=gcf.comments_id

 

看一下执行计划:

这种状况,因为咱们的关联字段是有索引的,因此说Index Nested-Loop Join,只不过当没有筛选条件的时候会选择第一张表做为驱动表去进行JOIN,去关联非驱动表的索引进行Index Nested-Loop Join。

若是加上筛选条件gc.comments_id =2056的话,这样就会筛选出一条对非驱动表进行Index Nested-Loop Join,这样效率是很高的。

若是是下面这种:

EXPLAIN SELECT * FROM comments_for gcf
LEFT JOIN comments gc ON gc.comments_id=gcf.comments_id
WHERE gcf.comments_id =2056

 

经过gcf表进行筛选的话,就会默认选择gcf表做为驱动表,由于很明显他进行过了筛选,匹配的条件会不多,具体能够看下执行计划:

此,join基本上已经很明了了,未完待续中,欢迎你们指出错误,我会认真改正。。。。

相关文章
相关标签/搜索