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 执行流程 :

  1. 根据索引 a,查找所有 ID,把 ID 放入 read_rnd_buffer
  2. 再对 read_rnd_buffer 的 id 进行排序
  3. 再根据排序后的 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 算法的影响 :

  1. 多次扫描被驱动表,占用磁盘 IO 资源
  2. 判断 join 要执行 M*N 次对比(M、N 分别是两张表的行数),当是大表就会占用较大的 CPU 资源
  3. 可能会导致 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 流程 :

  1. 把表 t1 的所有字段取出来,存入 join_buffer 中 (默认 : 256k)
  2. 扫描表 t2,取出每行数据与 join_buffer 的数据进行对比
    1. 当不满足 t1.b=t2.b,就跳过
    2. 当满足 t1.b=t2.b , 再判断是否满足 t2.b 在 [1,2000] 中,当是就返回,否则跳过

explain 结果 :

  • Extra 用了 BNL 算法

执行时间 :

用临时表优化 :

  1. 把表 t2 满足条件的数据放在临时表 tmp_t 中
  2. 在临时表 tmp_t 的字段 b 加上索引 , 就能用 BKA 算法
  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;

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

执行时间 : 总和 < 1 秒

过程消耗:

  1. insert 插入 temp_t 表中,会对表 t2 做了全表扫描 (100 万)
  2. Join 比较时,做了 1000 次带索引查询,该优化效果明显

Hash join

MySQL5 未实现 :在 join_buffer 构建哈希数据 , 每次查询就快了 (MySQL8 实现了)

应用端实现 :

  1. select * from t1; 取表 t1 的全部数据,存入 Hash 结构
  2. select * from t2 where b>=1 and b<=2000; 取表 t2 中满足条件的数据
  3. 把表 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过程详解

mysql 第四十七篇文章~mysql优化之相关join

mysql-STRAIGHT_JOIN-优化

MySQL对JOIN做了那些不为人知的优化

MySQL的JOIN:JOIN优化实践之内循环的次数

MYSQL之not in优化方法:left join