MySQL Join 优化
Posted cpuCode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL Join 优化相关的知识,希望对你有一定的参考价值。
mysql Join 优化
测试数据:
create table t1(
id int primary key,
a int, b int, index(a)
);
create table t2 like t1;
drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=1000)do
insert into t1 values(i, 1001-i, i);
set i=i+1;
end while;
set i=1;
while(i<=1000000)do
insert into t2 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
MRR
Multi-Range Read 优化 (MRR) : 尽量用顺序读盘
回表流程 :
- 一行行搜索主键索引
- id 值是随机,就会出现随机访问
MRR思想 :对主键的递增顺序查询,更接近顺序读,就能提升读性能
MRR 执行流程 :
- 根据索引 a,查找所有 ID,把 ID 放入
read_rnd_buffer
- 再对
read_rnd_buffer
的 id 进行排序 - 再根据排序后的 id ,查询
稳定用 MRR 优化 :
set optimizer_switch="mrr_cost_based=off"
控制 read_rnd_buffer 大小 :
read_rnd_buffer_size
explain :
- Using MRR : 用上 MRR 优化
BKA
MySQL 5.6 后引入 Batched Key Access(BKA) 算法 : 对 NLJ 算法的优化
NLJ 算法 :
- 每次都匹配一个值 , 无法利用 MRR
BKA 流程 :
- 把 a索引 放入 join_buffer , 再批次查询 ,就能利用 MRR
启动 BKA 优化算法
- 依赖 MRR
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
BNL 优化
Block Nested-Loop Join 算法,可能会对被驱动表做多次扫描。当被驱动表是个大量冷数据表,可能导致 IO 压力大,还可能影响 Buffer Pool 的命中率
BNL 对 Buffer Pool 的俩个影响 :
- 用 BNL 的 Join ,当执行时间超过 1 秒,并再次扫描冷表时,把冷表的数据页会移到 LRU 链表头部 , 影响 young 区
- 业务正常访问的数据页,没机会进入 young 区
为了减少影响,可以考虑增大 join_buffer_size
,减少对被驱动表的扫描次数
BNL 算法的影响 :
- 多次扫描被驱动表,占用磁盘 IO 资源
- 判断 join 要执行 M*N 次对比(M、N 分别是两张表的行数),当是大表就会占用较大的 CPU 资源
- 可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率
查看 explain 结果
- 确认是否用了 BNL 算法
- 当用了 BNL 算法,尽量优化成 BKA 算法。对被驱动表的 Join 字段加上索引
BNL 转 BKA
不适合在被驱动表上建索引情况 :
- where 后, 只有 2000 行数据
- 低频 SQL , 在 t2.b 上建索引较浪费
select * from t1 join t2 on (t1.b = t2.b)
where t2.b >= 1 and t2.b <= 2000;
BNL 算法 Join 流程 :
- 把表 t1 的所有字段取出来,存入
join_buffer
中 (默认 : 256k) - 扫描表 t2,取出每行数据与 join_buffer 的数据进行对比
- 当不满足 t1.b=t2.b,就跳过
- 当满足 t1.b=t2.b , 再判断是否满足 t2.b 在 [1,2000] 中,当是就返回,否则跳过
explain 结果 :
- Extra 用了 BNL 算法
执行时间 :
用临时表优化 :
- 把表 t2 满足条件的数据放在临时表 tmp_t 中
- 在临时表 tmp_t 的字段 b 加上索引 , 就能用 BKA 算法
- 让表 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;
select * from t1 join temp_t on (t1.b = temp_t.b);
执行时间 : 总和 < 1 秒
过程消耗:
- insert 插入 temp_t 表中,会对表 t2 做了全表扫描 (100 万)
- Join 比较时,做了 1000 次带索引查询,该优化效果明显
Hash join
MySQL5 未实现 :在 join_buffer 构建哈希数据 , 每次查询就快了 (MySQL8 实现了)
应用端实现 :
select * from t1;
取表 t1 的全部数据,存入 Hash 结构select * from t2 where b>=1 and b<=2000;
取表 t2 中满足条件的数据- 把表 t2 的每条数据,在 Hash 数据中 , 找匹配的数据
MySQL的JOIN:JOIN优化实践之内循环的次数
这篇博文讲述如何优化内循环的次数。内循环的次数受驱动表的记录数所影响,驱动表记录数越多,内循环就越多,连接效率就越低下,所以尽量用小表驱动大表。先插入测试数据。
CREATE TABLE t1 ( id INT PRIMARY KEY AUTO_INCREMENT, type INT ); SELECT COUNT(*) FROM t1; +----------+ | COUNT(*) | +----------+ | 10000 | +----------+ CREATE TABLE t2 ( id INT PRIMARY KEY AUTO_INCREMENT, type INT ); SELECT COUNT(*) FROM t2; +----------+ | COUNT(*) | +----------+ | 100 | +----------+
内连接谁当驱动表
实际业务场景中,左连接、右连接可以根据业务需求认定谁是驱动表,谁是被驱动表。但是内连接不同,根据嵌套循环算法的思想,t1内连接t2和t2内连接t1所得结果集是相同的。那么到底是谁连接谁呢?谨记一句话即可,小表驱动大表可以减小内循环的次数。下面用 STRAIGHT_JOIN强制左表连接右表。By the way,STRIGHT_JOIN比较冷门,在这里解释下,其作用相当于内连接,不过强制规定了左表驱动右边。详情看这MySQL的JOIN(一):用法
EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.type=t2.type; +----+-------+------+------+-------+----------------------------------------------------+ | id | table | type | key | rows | Extra | +----+-------+------+------+-------+----------------------------------------------------+ | 1 | t1 | ALL | NULL | 10000 | NULL | | 1 | t2 | ALL | NULL | 100 | Using where; Using join buffer (Block Nested Loop) | +----+-------+------+------+-------+----------------------------------------------------+ EXPLAIN SELECT * FROM t2 STRAIGHT_JOIN t1 ON t2.type=t1.type; +----+-------+------+------+-------+----------------------------------------------------+ | id | table | type | key | rows | Extra | +----+-------+------+------+-------+----------------------------------------------------+ | 1 | t2 | ALL | NULL | 100 | NULL | | 1 | t1 | ALL | NULL | 10000 | Using where; Using join buffer (Block Nested Loop) | +----+-------+------+------+-------+----------------------------------------------------+
对于第一条查询语句,t1是驱动表,其有10000条记录,内循环也就有10000次,这还得了?
对于第二条查询语句,t2是驱动表,其有100条记录,内循环100次,感觉不错,我喜欢!
这些SQL语句的执行时间也说明了,当内连接时,务必用小表驱动大表。
最佳实践:直接让MySQL去判断
但是,表的记录数是会变化的,有没有一劳永逸的写法?当然有啦,MySQL自带的Optimizer会优化内连接,优化策略就是上面讲的小表驱动大表。所以,以后写内连接不要纠结谁内连接谁了,直接让MySQL去判断吧。
EXPLAIN SELECT * FROM t1 INNER JOIN t2 ON t1.type=t2.type; EXPLAIN SELECT * FROM t2 INNER JOIN t1 ON t1.type=t2.type; EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.type=t2.type; EXPLAIN SELECT * FROM t2 JOIN t1 ON t1.type=t2.type; EXPLAIN SELECT * FROM t1,t2 WHERE t1.type=t2.type; EXPLAIN SELECT * FROM t2,t1 WHERE t1.type=t2.type; +----+-------+------+------+--------+----------------------------------------------------+ | id | table | type | key | rows | Extra | +----+-------+------+------+--------+----------------------------------------------------+ | 1 | t2 | ALL | NULL| 100 | NULL | | 1 | t1 | ALL | NULL | 110428 | Using where; Using join buffer (Block Nested Loop) | +----+-------+------+------+--------+----------------------------------------------------+
上面6条内连接SQL,MySQL的Optimizer都会进行优化。
以上是关于MySQL Join 优化的主要内容,如果未能解决你的问题,请参考以下文章
Mysql 优化器内部JOIN算法hash join Nestloopjoin及classic hash join CHJ过程详解