35 | join语句怎么优化?(问题未完待续)

NLJ 效果不错,BNL 大表 join 性能差,消耗 CPU 资源。都可继续优化

35 | join语句怎么优化?(问题未完待续)_第1张图片

 t1 1000 行, a=1001-id 的值。a逆序。 t2 插入了 100 万行数据。

一、Multi-Range Read 优化

Read 优化 (MRR),顺序读盘。

第 4 篇文章“回表”普通索引 a 上查到主键 id ,根据主键 id 去查整行数据的过程。

select * from t1  where a>=1 and a<=100;

主键索引 B+ 树,每次只能根据一个主键 id 查一行一行行搜主键索引

35 | join语句怎么优化?(问题未完待续)_第2张图片
图 1 基本回表流程

a 递增,id 变随机,随机访问,性能。虽按行查不能改,调整顺序加速

MRR思路:主键递增,磁盘读接近顺序读,提升读性能。

1.  索引 a,定位满足条件记录,id 值放read_rnd_buffer 中;

2.  read_rnd_buffer 中 id 进行递增排序

3.   id 记录返回

read_rnd_buffer_size 控制大小。read_rnd_buffer放满,清空继续循环。

稳定用 MRR 优化设置setoptimizer_switch="mrr_cost_based=off"。(优化器策略,判断消耗时,倾向不使用 MRR,就是固定用 MRR )

35 | join语句怎么优化?(问题未完待续)_第3张图片
图 2 MRR 执行流程  
图 3 MRR 执行流程的 explain 结果

 Extra 多 Using MRR。read_rnd_buffer 按id 排序,结果集中行顺序相反

提升性能核心顺序性”:a 上查 id。排序,id查数据,

二、Batched Key Access

NLJ 的优化(放内存)

35 | join语句怎么优化?(问题未完待续)_第4张图片
图 4 Index Nested-Loop Join 流程图

表 t2 每次都匹配一个值。MRR 用不上。

t1 多拿行一起传,先放临时内存join_buffer暂存驱动表数据

35 | join语句怎么优化?(问题未完待续)_第5张图片
图 5 Batched Key Access 流程

放入 P1~P100,只会取查询需要字段。如放不下所有,分多段

启用先设置set optimizer_switch = 'mrr=on,mrr_cost_based=off,batched_key_access=on';  启用 MRRBKA 依赖于 MRR。

三、BNL 算法性能问题

被驱动表做多次扫描。大冷数据表,除导致 IO 压力大外,还有什么影响?

LRU 优化, old 区域。对 Buffer Pool 的命中率影响就不大。

冷表很大,出现另外一种情况:业务正常访问数据页,没有机会进入 young 区域(没有被合理淘汰)。

两种情况都影响 Buffer Pool 正常运作。

大表 join 操作虽然对 IO 有影响,语句执行结束后,对 IO 的影响结束。Buffer Pool 影响持续性,后续的查询慢慢恢复内存命中率。

增大join_buffer_size 的值,减少被驱动表扫描次数。BNL 影响:

1.  占用磁盘 IO 资源;

2.  判断 join 条件需M*N 次对比(M、N 分别是两张表的行数),占用 CPU 资源;

3.  Buffer Pool 热数据淘汰,影响内存命中率。

优化:被驱动表 join 字段索引,BNL 转BKA 算法。

四、BNL BKA

被驱动表索引直接转BKA不适合在被驱动表上建索引如:

select * from t1  join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000

t2 插 100 万,where后只有 2000 。同时低频的 SQL ,建索引就浪费。

1.  t1 所有字段取出来,存入 join_buffer 。1000行 完全存入 join_buffer_size(默认 256k)

2.  扫描t2,跟 join_buffer 对比不满足 t1.b=t2.b跳过[1,2000] 返回,否则跳过。

对于表 t2 每行,判断 join 是否满足的时候,遍历 join_buffer 所有行。判断等值条件的次数是 1000*100 万 =10 亿次,工作量大。

图 6 explain 结果
35 | join语句怎么优化?(问题未完待续)_第6张图片
图 7 语句执行时间

Extra 显示用 BNL 算法。执行 1 分 11 秒。

 b 上创建索引浪费资源,不创建判断 10 亿次,浪费。两全其美办法:临时表:

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

2.  让 join 用 BKA 算法,给临时表 tmp_t 字段 b 加索引

3.  t1 和 tmp_t 做 join 操作。

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;//100 万

select * from t1  join temp_t on (t1.b=temp_t.b); //1000次索引查询

35 | join语句怎么优化?(问题未完待续)_第7张图片
图 8 使用临时表的执行效果

原表上加索引,还是索引临时表,join 用被驱动表上索引,触发 BKA 算法,提升性能。

五、扩展-hash join

上面计算 10 亿次点儿傻。如 join_buffer 不是无序数组,是哈希表话,不是 10 亿判断,而是 100 万次 hash 查找。快多了(比临时表快

MySQL 优化器和执行器诟病:不支持哈希 join。优化思路自己实现:

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 结构数据表中寻找匹配数据作为结果集

小结

Index Nested-Loop Join(NLJ)和 Block Nested-Loop Join(BNL)的优化方法。

1.  BKA 优化是 MySQL 已经内置支持,默认用;

2.  BNL 效率低,建议你都尽量转成 BKA 算法。给被驱动表关联字段加上索引

3.  临时表改进方案,提前过滤出小数据 join ,效果好;

4.  MySQL 不支持 hash join,自己模拟,好于临时表

思考题

三个表 join 需求:

35 | join语句怎么优化?(问题未完待续)_第8张图片

改写成straight_join,怎么指定连接顺序,怎么给三个表创建索引

第一原则: BKA 算法,直接嵌套查询。不是“先计算两个表 join 结果,再跟第三个join”,

实现:t1.c>=X、t2.c>=Y、t3.c>=Z 三个条件,数据最少第一驱动表

第一种情况

选出表 t1 或者 t3,剩下部分就固定。

1. 驱动表t1,连接顺序t1->t2->t3被驱动表t2.a 和 t3.b )字段建索引, 

2.  驱动表t3,连接顺序t3->t2->t1t2.b 和 t1.a 上创建索引。

驱动表 c 创建索引。

第二种情况

第一个驱动表t2,评估另外两个条件过滤效果。

评论1

select * from t1 join t2 on(t1.a=t2.a) 

join t3 on (t2.b=t3.b) 

where t1.c>=X and t2.c>=Y and t3.c>=Z;

快速定位t1、t2和t3的c字段建索引。join优先选择数量比较少两张表,t1.a=t2.a的行数小于满足t2.b=t3.b的行数,t1和t2join,行数少驱动表,被驱动表a上建立索引,结果放入join_buffer与t3进行join,被驱动表的t3的b上建索引。t1.c>=X ,t2.c>=Y ,t3.c>=Z

为什么不用一个索引,要分别建3个?

评论2

我还有个问题,开篇sql select * from t1 where a>=1 and a<=100;

a是索引列,有order by a,不使用MRR 优化,查询出来就是按a排序的,使用了mrr优化,是不是要额外排序?

答:用order by就不用MRR

ps: 固态硬盘顺序写随机写快

你可能感兴趣的:(35 | join语句怎么优化?(问题未完待续))