0
点赞
收藏
分享

微信扫一扫

《MySQL——join语句优化tips》


目录

  • ​​要不要用join​​
  • ​​Join驱动表选择​​
  • ​​Multi-Range Read优化​​
  • ​​Batched Key Access (BKA)对NLJ进行优化​​
  • ​​BNL算法性能问题​​
  • ​​BNL转BKA​​


要不要用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的各个字段的总数据量,数据量小的表,那就是小表。

Multi-Range Read优化

若有这样查询语句:

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

a值是递增的,但是回表后的id并非如此,而是随机的,会带来性能损失。

大多数数据按照主键递增顺序插入得到,所以我们可以认为如果按照主键的递增顺序查找的话,对磁盘的读比较接近顺序读,从而可以提升读性能。

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

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

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

总的来说就是:**先将索引数据缓存,查到id之后,排序之后再回表 **

用法:

设置:

set optimizer_switch="mrr_cost_based=off

现在的优化器在判断消耗时,更倾向于不使用MRR,所以需要设置为off后,就会固定使用MRR

Batched Key Access (BKA)对NLJ进行优化

​Index Nested-Loop Join​​执行逻辑是:从驱动表t1,一行行取出a值,再到驱动表t2去做join。对于表t2来说,每次都是匹配一个值,MMR优势用不上。

既然这样,将表t1的数据取出来一部分,先放到一个临时内存里:join_buffer.

然后在此基础上复用MRR即可。

使用方法:

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

BNL算法性能问题

之前提到过InnoDB的LRU优化:第一次从磁盘读入内存的数据页,会先放到old区域,如果1s后这个数据页不再被访问,就不会移动到LRU链表头部,这样对Buffer Pool命中率影响就不大了。

如果使用了BNL的join语句,多次扫描一个冷表,并且这个语句执行时间超过1s,就会在再次扫描冷表时,把冷表的数据页移动到LRU链表头部。

如果冷表数据很大, 会一直占据old区,正常页无法进入,无法更新young区
tips: 冷表,指表中数据还没有加载到bufferpool中,需要先从盘里读出来的表
又因为优化机制,一个正常访问的数据页要进入young区域,需要隔1s再次被访问到。由于join’语句在循环都磁盘和淘汰内存页,进入old区域的数据页很可能在1s之内就被淘汰了。

大表join后对于Buffer Pool的影响是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。

总结,BNL对于系统的影响:

1、可能多次扫描被驱动表,占用磁盘IO资源

2、判断join条件执行M * N次,占用CPU资源

3、可能导致Buffer Pool的热数据被淘汰,影响内存命中率

所以我们需要优化BNL,通过给驱动表的join字段加索引的方式,将BNL转换为BKA

BNL转BKA

对于一些不常执行大表join的sql,不在被驱动表上创建索引的情况,可以创建一个临时表 create templete table在这个临时表上创建索引,然后让驱动表与临时表做join操作。 为什么不在被驱动表上创建索引,是因为这块sql功能不常用,创建索引浪费空间,并且可能触发这块的join sql 也不经常调用。

创建临时表以及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;
select * from t1 join temp_t on (t1.b=temp_t.b);


举报

相关推荐

0 条评论