个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈
今天我们来看一下join语句的执行流程
JOIN主要使用 Index Nested-Loop Join 和 Block Nested-Loop Join 算法实现
如果 join on 相关的字段存在索引就使用 Index Nested-Loop Join 算法来进行关联
如下sql语句的执行过程:
select * from t1 join t2 on (t1.a=t2.a);
假设不使用 join,那我们就只能用单表查询。我们看看上面这条语句的需求,用单表查询 怎么实现。
可以看到,在这个查询过程,也是扫描了 200 行,但是总共执行了 101 条语句,比直接 join 多了 100 次交互。除此之外,客户端还要自己拼接 SQL 语句和结果
显然,这么做还不如直接 join 好。
怎么选择驱动表?
假设驱动表的行数是 N,执行过程就要扫描驱动表 N 行,然后对于每一行,到被驱动表上匹配一次
因此整个执行过程,近似复杂度是 N + N2log2M。显然,N 对扫描行数的影响更大,因此应该让小表来做驱动表
结论:
但是,你需要注意,这个结论的前提是“可以使用被驱动表的索引”。
Block Nested-Loop Join
如果关联语句中没有索引的话 可能需要扫描 N*M 行 当然Mysql对此有一个优化算法
算法的流程是这样的:
Block Nested-Loop Join 算法的这 N*M 次判断是内存操作,速度上会快很多,性能也更好
接下来,我们来看一下,在这种情况下,应该选择哪个表做驱动表。
可以看到,调换这两个算式中的 M 和 N 没差别,因此这时候选择大表还是小表做驱动 表,执行耗时是一样的。
join_buffer 放不下怎么办
如果放不下表 t1 的所有数据话,策略很简单,就是分段放
执行过程就变成了:
假设,驱动表的数据行数是 N,需要分 K 段才能完成算法流程,被驱动表的数据行数是 M。 注意,这里的 K 不是常数,N 越大 K 就会越大,因此把 K 表示为λ*N,显然λ的取值范围 是 (0,1)。 所以,在这个算法的执行过程中:
显然,内存判断次数是不受选择哪个表作为驱动表影响的。而考虑到扫描行数,在 M和 N大小确定的情况下,N小一些,整个算式的结果会更小。
所以结论是,应该让小表当驱动表。
这里我需要说明下,什么叫作“小表”。
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤, 过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表
Multi-Range Read 优化
在介绍 join 语句的优化方案之前,我需要先介绍一个知识点,即:Multi-Range Read 优化 (MRR)。这个优化的主要目的是尽量使用顺序读盘
因为大多数的数据都是按照主键递增顺序插入得到的,所以我们可以认为,如果按照主键 的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能
这,就是 MRR 优化的设计思路。此时,语句的执行流程变成了这样:
另外需要说明的是,如果你想要稳定地使用 MRR 优化的话,需要设置
set optimizer_switch="mrr_cost_based=off"
MRR 能够提升性能的核心在于,这条查询语句在索引 a 上做的是一个范围查询可以得到足够多的主键 id。这样通过排序以后,再去主键索引查数据,才能体现出“顺序性”的优势
Batched Key Access
这个 Batched Key Access (BKA),其实就是对 NLJ 算法的优化
NLJ 算法执行的逻辑是:从驱动表 t1,一行行地取出 a 的值,再到被驱动表 t2 去做 join。也就是说,对于表 t2 来说,每次都是匹配一个值。这时,MRR 的优势就用不上了
那怎么才能一次性地多传些值给表 t2呢?方法就是,从表 t1 里一次性地多拿些行出来, 一起传给表 t2。 既然如此,我们就把表t1 的数据取出来一部分,先放到 join_buffer。 我们知道 join_buffer 在 BNL 算法里的作用,是暂存驱动表的数据。但是在 NLJ 算法里并没有用。那么,我们刚好就可以复用 join_buffer 到 BKA 算法中。
如果要使用 BKA 优化算法的话,你需要在执行 SQL 语句之前,先设置
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
BNL 算法的性能问题
大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率
为了减少这种影响,你可以考虑增大 join_buffer_size 的值,减少对被驱动表的扫描次数。
也就是说,BNL 算法对系统的影响主要包括三个方面:
我们执行语句之前,需要通过理论分析和查看 explain 结果的方式,确认是否要使用 BNL 算法。如果确认优化器会使用 BNL 算法,就需要做优化。优化的常见做法是,给被驱动表 的 join 字段加上索引,把 BNL 算法转成 BKA 算法
hash join
如果 join_buffer 里面维护的不是一个无序数组,而是一个哈希表的话,那么就不是 10 亿次判 断,而是 100 万次 hash 查找。这样的话,整条语句的执行速度就快多了吧?
实际上,这个优化思路,我们可以自己实现在业务端。实现流程大致如下:
附Java/C/C++/机器学习/算法与数据结构/前端/安卓/Python/程序员必读/书籍书单大全:
(点击右侧 即可打开个人博客内有干货):技术干货小栈
=====>>①【Java大牛带你入门到进阶之路】<<====
=====>>②【算法数据结构+acm大牛带你入门到进阶之路】<<===
=====>>③【数据库大牛带你入门到进阶之路】<<=====
=====>>④【Web前端大牛带你入门到进阶之路】<<====
=====>>⑤【机器学习和python大牛带你入门到进阶之路】<<====
=====>>⑥【架构师大牛带你入门到进阶之路】<<=====
=====>>⑦【C++大牛带你入门到进阶之路】<<====
=====>>⑧【ios大牛带你入门到进阶之路】<<====
=====>>⑨【Web安全大牛带你入门到进阶之路】<<=====
=====>>⑩【Linux和操作系统大牛带你入门到进阶之路】<<=====天下没有不劳而获的果实,望各位年轻的朋友,想学技术的朋友,在决心扎入技术道路的路上披荆斩棘,把书弄懂了,再去敲代码,把原理弄懂了,再去实践,将会带给你的人生,你的工作,你的未来一个美梦。