微信公众号:Java随想录
- 摘要
- 什么是Join
- Index Nested-Loop Join
- Block Nested-Loop Join
- MRR & BKA
- 总结
摘要
什么是Join
在MySQL中,Join是一种用于组合两个或多个表中数据的查询操作。Join操作通常基于两个表中的某些共同的列进行,这些列在两个表中都存在。MySQL支持多种类型的Join操作,如Inner Join、Left Join、Right Join、Full Join等。
SELECT *
FROM customers
INNER JOIN orders
ON customers.customer_id = orders.customer_id;
上面的查询将返回所有存在于“customers”和“orders”表中的“customer_id”列相同的行。
Index Nested-Loop Join
工作流程如图:
select * from t1 straight_join t2 on (t1.a=t2.a;
在这个语句里,假设t1 是驱动表,t2是被驱动表。我们来看一下这条语句的explain结果。
- 从表t1中读入一行数据 R;
- 从数据行R中,取出a字段到表t2里去查找;
- 取出表t2中满足条件的行,跟R组成一行,作为结果集的一部分;
- 重复执行步骤1到3,直到表t1的末尾循环结束。
这个过程就跟我们写程序时的嵌套查询类似,并且可以用上被驱动表的索引,所以我们称之为“Index Nested-Loop Join”,简称NLJ。
MySQL会选择使用另一个叫作“Block Nested-Loop Join”的算法,简称BNL。
Block Nested-Loop Join
例如,下面这个语句:
select * from t1 straight_join t2 on (t1.a=t2.b;
字段b上是没有建立索引的。
- 把表t1的数据读入线程内存join_buffer中,由于我们这个语句中写的是select *,因此是把整个表t1放入了内存;
- 扫描表t2,把表t2中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。
这条SQL语句的explain结果如下所示:
虽然Block Nested-Loop Join算法是全表扫描。但是是在内存中进行的判断操作,速度上会快很多。但是性能仍然不如NLJ。
如果放不下表t1的所有数据话,策略很简单,就是分段放。
- 顺序读取数据行放入join_buffer中,直到join_buffer满了。
- 扫描被驱动表跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。
- 清空join_buffer,重复上述步骤。
MRR & BKA
上篇文章里我们讲到了MRR(Multi-Range Read)。MySQL在5.6版本后引入了Batched Key Acess(BKA算法了。这个BKA算法,其实就是对NLJ算法的优化,BKA算法正是基于MRR。
我们可以从表t1里一次性地多拿些行出来,先放到一个临时内存,一起传给表t2。这个临时内存不是别人,就是join_buffer。
NLJ算法优化后的BKA算法的流程,如图所示:
如果要使用BKA优化算法的话,你需要在执行SQL语句之前,先设置
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
其中,前两个参数的作用是要启用MRR。这么做的原因是,BKA算法的优化要依赖于MRR。
例如,对于这个语句:
select * from t1 join t2 on (t1.b=t2.b where t2.b>=1 and t2.b<=2000;
使用临时表的大致思路是:
- 把表t2中满足条件的数据放在临时表tmp_t中;
- 为了让join使用BKA算法,给临时表tmp_t的字段b加上索引;
- 让表t1和tmp_t做join操作。
总结
在MySQL中,不管Join使用的是NLJ还是BNL总是应该使用小表做驱动表。更准确地说,在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。应当尽量避免使用BNL算法,如果确认优化器会使用BNL算法,就需要做优化。优化的常见做法是,给被驱动表的join字段加上索引,把BNL算法转成BKA算法。对于不好在索引的情况,可以基于临时表的改进方案,提前过滤出小数据添加索引。