mysql的join算法叫做Nested-Loop Join(嵌套循环连接)
而这个Nested-Loop Join有三种变种,下面分别介绍下
这个算法相当简单、直接。即驱动表中的每一条记录与被驱动表中的记录进行比较判断(就是个笛卡尔积)。对于两表联接来说,驱动表只会被访问一遍,但被驱动表却要被访问到好多遍
假设R为驱动表,S被驱动表,用伪代码表示一下这个过程就是这样:
for r in R # 扫描R表(驱动表)
for s in S # 扫描S表(被驱动表)
if (r and s satisfy the join condition) # 如果r和s满足join条件
output result # 返回结果集
所以如果R有1万条数据,S有1万条数据,那么数据比较的次数1万 * 1万 =1亿次
,这种查询效率会非常慢。
这个是基于索引进行连接的算法
它要求被驱动表上有索引,可以通过索引来加速查询。
假设R为驱动表,S被驱动表,用伪代码表示一下这个过程就是这样:
For r in R # 扫描R表
for s in Sindex # 查询S表的索引(固定3~4次IO,B+树高度)
if (s == r) # 如果r匹配了索引s
output result # 返回结果集
这个算法较Simple Nested-Loop Join的改进就在于可以减少被驱动表的扫描次数
因为它使用Join Buffer来减少内部循环读取表的次数
假设R为驱动表,S被驱动表,用伪代码表示一下这个过程就是这样:
for r in R # 扫描表R
store p from R in Join Buffer # 将部分或者全部R的记录保存到Join Buffer中,记为p
for s in S # 扫描表S
if (p and s satisfy the join condition) # p与s满足join条件
output result # 返回为结果集
可以看到相比Simple Nested-Loop Join算法,Block Nested-LoopJoin算法仅多了一个所谓的Join Buffer
为什么这样就能减少被驱动表的扫描次数呢?
下图相比更好地解释了Block Nested-Loop Join算法的运行过程
可以看到Join Buffer用以缓存联接需要的列(所以再次提醒我们,最好不要把*作为查询列表,只需要把我们关心的列放到查询列表就好了,这样还可以在join buffer中放置更多的记录呢,是不是这个道理哈,哈哈)
然后以Join Buffer批量的形式和被驱动表中的数据进行联接比较。
关于Join Buffer
join_buffer_size
的默认值是256K在选择Join算法时,会有优先级:
Index Nested-LoopJoin > Block Nested-Loop Join > Simple Nested-Loop Join
当不使用Index Nested-Loop Join
的时候,默认使用Block Nested-Loop Join
。
使用Block Nested-Loop Join算法需要开启优化器管理配置的optimizer_switch
的设置block_nested_loop
为on,默认为开启。
通过上面的简单介绍,可以总结出以下几种优化思路
1.用小结果集驱动大结果集,减少外层循环的数据量
2.如果小结果集和大结果集连接的列都是索引列,mysql在join时也会选择用小结果集驱动大结果集,因为索引查询的成本是比较固定的,这时候外层的循环越少,join的速度便越快。
3.为匹配的条件增加索引:争取使用Index Nested-Loop Join,减少内层表的循环次数
4.增大join buffer size
的大小:当使用Block Nested-Loop Join时,一次缓存的数据越多,那么外层表循环的次数就越少,减少不必要的字段查询:
5.当用到Block Nested-Loop Join时,字段越少,join buffer 所缓存的数据就越多,外层表的循环次数就越少;