MySQL系列之Join大法

1. Index Nested-Loop Join

概念解释:

假设有t1,t2两张表,在join连接的时候,t1表驱动t2表,t1走的全部扫描,t2表使用了索引,

则这个时候join就使用了“index nested-loop join”算法,简称:NLJ。

NLJ执行的流程如下:


Index Nested-Loop Join 算法的执行流程

2. Block Nested-Loop Join

基于上面t1表和t2表,join连接,t1表和t2表都没有命中索引,都是走的全部扫描。这个

时候Join使用的是“Block Nested-Loop Join”算法,简称:BNL。

BNL算法的流程如下:


Block Nested-Loop Join 算法的执行流程


在explain分析语句的时候,extra附加信息中会出现“Using join buffer (Block Nested Loop)”。

join_buffer是由参数join_buffer_size决定的。默认是256k。如果放不下表 t1

的所有数据话,策略很简单,就是分段放。

流程大概是:

取t1表的数据,放入join_buffer中,如果join buffer满了,就扫描t2表的数据,跟join buffer

的数据进行对比,满足join条件的作为结果集返回;清空join buffer,继续上面的过程。

能否使用Join?

1. 如果可以使用 Index Nested-Loop Join 算法,也就是说可以用上被驱动表上的索引,

其实是没问题的;

2.  如果使用 Block Nested-Loop Join 算法,扫描行数就会过多。尤其是在大表上的 join

操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。所以这种 join 尽量不

要用。

如果要使用 join,应该选择大表做驱动表还是选择小表做驱动表?

1. 如果是 Index Nested-Loop Join 算法,应该选择小表做驱动表;

2. 如果是 Block Nested-Loop Join 算法:

    在 join_buffer_size 足够大的时候,是一样的;

    在 join_buffer_size 不够大的时候(这种情况更常见),应该选择小表做驱动表。

    这个问题的结论就是,总是应该使用小表做驱动表。

注意:join慢的时候,尽量跳大join_buffer_size的值。

优化Join

1. 优化NLJ算法

Multi-Range Read 优化算法 (MRR)。这个优化的主要目的是尽量使用顺序读盘

如果随着辅助索引(二级索引) a 的值递增顺序查询的话,主键索引id 的值就变成随机的,

那么就会出现随机访问,性能相对较差。虽然“按行查”这个机制不能改,但是调整查询的顺序,

还是能够加速的。这就是 MRR 优化的设计思路。

加入了MRR优化的执行流程如下:

1. 根据索引 a,定位到满足条件的记录,将 id 值放入 read_rnd_buffer 中 ;

2. 将 read_rnd_buffer 中的 id 进行递增排序;

3. 排序后的 id 数组,依次到主键 id 索引中查记录,并作为结果返回。

read_rnd_buffer 的大小是由 read_rnd_buffer_size 参数控制的。

启用MRR算法:

set optimizer_switch="mrr_cost_based=off"

Batched Key Access (BKA) 算法

MySQL 在 5.6 引入的,是对 NLJ 算法的优化。

NLJ 算法优化后的 BKA 算法的流程如下:


Batched Key Acess 流程

启用 BKA 算法:

set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

前两个参数的作用是要启用 MRR。这么做的原因是,BKA 算法的优化要依赖于MRR。

BNL 算法的优化

优化的常见做法是,给被驱动表的 join 字段加上索引,把 BNL 算法转成 BKA 算法。

但是如果无法在被驱动表上加索引,那么:

考虑使用临时表,使用临时表的大致思路是:

1. 把表 t2 中满足条件的数据放在临时表 tmp_t 中;

2. 为了让 join 使用 BKA 算法,给临时表 tmp_t 的字段 b 加上索引;

3. 让表 t1 和 tmp_t 做 join 操作。

SQL语句为:

create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;

insert into temp_t select * from t2 where b>=1 and b<=2000;

select * from t1 join temp_t on (t1.b=temp_t.b);

总体来看,不论是在原表上加索引,还是用有索引的临时表,我们的思路都是让 join 语句

能够用上被驱动表上的索引,来触发 BKA 算法,提升查询性能。

上面如果不适用临时表进行优化,那么还有其他方式进行优化?

我们可以自己实现在业务端。实现流程大致如下:

1. select * from t1;取得表 t1 的全部 1000 行数据,在业务端存入一个 hash 结构,

比如 C++ 里的 set、PHP 的数组这样的数据结构。

2. select * from t2 where b>=1 and b<=2000; 获取表 t2 中满足条件的 2000 行

数据。

3. 把这 2000 行数据,一行一行地取到业务端,到 hash 结构的数据表中寻找匹配的数

据。满足匹配的条件的这行数据,就作为结果集的一行。

关于临时表的几个问题:

1.  binlog_format=row,那么跟临时表有关的语句,就不会记录到binlog 里。只在 

binlog_format=statment/mixed 的时候,binlog 中才会记录临时表的操作。

2. 在使用临时表的时候,最后最好要写上 DROP TEMPORARY TABLE,删除临时表

3. 临时表只对本session会话可见,对其他的session不可见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容