学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • Java基础

  • Java集合

  • MySQL

    • MySQL深入01-查询语句
    • MySQL深入02-更新语句
    • MySQL深入03-事务隔离
    • MySQL深入04-深入浅出索引
    • MySQL深入05-全局锁、表锁、行锁
    • MySQL深入06-事务隔离再探
    • MySQL深入07-普通索引和唯一索引
    • MySQL深入08-为什么会选错索引
    • MySQL深入09-字符串字段加索引
    • MySQL深入10-脏页刷新
    • MySQL深入11-数据库表空间回收
    • MySQL深入12-count()
    • MySQL深入13-order by
    • MySQL深入14-正确显示随机消息
    • MySQL深入15-索引失效案例分析
    • MySQL深入16-查询一行数据执行慢
    • MySQL深入17-幻读
    • MySQL深入18-改一行语句锁问题
    • MySQL深入19-暂时提高数据库性能方案
    • MySQL深入20-这么保证数据不丢
    • MySQL深入21-主备一致的保证
    • MySQL深入22-高可用性的保证
    • MySQL深入23-备库延迟好几个小时
    • MySQL深入24-主库出问题,从库这么办
    • MySQL深入25-读写分离的过期读问题
    • MySQL深入26-判断数据库是否出问题
    • MySQL深入27-误删数据的处理方案
    • MySQL深入28-kill不掉的语句
    • MySQL深入29-查询对内存的影响
    • MySQL深入30-join深入
    • MySQL深入31-join语句优化
      • MySQL深入31-join语句优化
      • Multi-Range Read优化
      • Batched Key Access
      • BNL算法优化
        • BNL算法性能问题
        • BNL转BKA
      • 参考
    • MySQL深入32-临时表深入
    • MySQL深入33-内部临时表何时使用
    • MySQL深入34-InnoDB和Memory
    • MySQL深入35-自增主键为什么不连续
    • MySQL深入36-insert语句的锁
    • MySQL深入37-如何快速复制一张表
    • MySQL深入38-grant和flush privileges
    • MySQL深入39-分区表
    • MySQL深入40-自增id用完如何处理
  • Redis

  • JVM

  • 多线程

  • 计算机网络

  • Spring

  • Kafka

  • Elasticsearch

  • Python

  • 面试专题

  • 知识库
  • MySQL
旭日
2023-03-31
目录

MySQL深入31-join语句优化

# MySQL深入31-join语句优化

相关文章:

MySQL深入30-join使用 (opens new window)

join语句有两种算法,分别是Index Nested-Loop Join(NLJ) 和 Block Nested-Loop Join(BNL)。

其中NLJ算法效果还是不错,比通过应用层拆分成多个语句然后再拼接查询结果更方便,而且性能也不会差。但是BNL算法在大表join的时候性能就差很多了,比较次数等于两个表参与join的行数的乘积,很消耗CPU资源。

# Multi-Range Read优化

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

InnoDB索引结构会涉及到“回表”,回表是指,InnoDB 在普通索引 a 上查到主键 id 的值后,再根据一个个主键 id 的值到主键索引上去查整行数据的过程。

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

主键索引是一棵B+树,在这棵树上,每次只能根据一个主键id查到一行数据,回表肯定是一行行搜索主键索引的。

image-20220701142535541

如果随着 a 的值递增顺序查询的话,id 的值就变成随机的,那么就会出现随机访问,性能相对较差。但是如果我们回表查询能够按照主键的递增顺序查询的话,那么磁盘的读比较接近顺序读,能够提升读性能。在MRR优化之后,语句的执行流程如下:

  • 根据索引 a,定位到满足条件的记录,将 id 值放入 read_rnd_buffer 中 ;
  • 将 read_rnd_buffer 中的 id 进行递增排序;
  • 排序后的 id 数组,依次到主键 id 索引中查记录,并作为结果返回。

这里,read_rnd_buffer 的大小是由 read_rnd_buffer_size 参数控制的。如果步骤 1 中,read_rnd_buffer 放满了,就会先执行完步骤 2 和 3,然后清空 read_rnd_buffer。之后继续找索引 a 的下个记录,并继续循环。

image-20220701143159274

MRR能够提升性能的核心在于,得到的非主键索引列表排序后再去主键索引差数据,体现出“顺序性”的优势。

# Batched Key Access

首先我们来回顾一下NLJ算法的流程:从驱动表t1,一行行地取出a的值,再到被驱动表t2去做join,对于表t2来说,每次相当于是匹配一个值。

image-20220701144057288

如果要使用MRR对其优化,就需要一次性多传值给表t2,也就是说表t1并不能和之前那样每次只取一个值而是要一次性取多行出来,一起传给表t2。t1的数据取出来的那部分存放到一个临时内存,这个临时内存就是join buffer。

join_buffer 在 BNL 算法里的作用,是暂存驱动表的数据。但是在 NLJ 算法里并没有用。

image-20220701144511169

当join buffer放不下一定数量的数据的时候,就会把这一定数量的数据分成多段执行上图的流程。

# BNL算法优化

# BNL算法性能问题

BNL算法无论是对驱动表还是对被驱动表都会进行全表扫描,这样会导致IO压力过多。同时还存在下面一个问题:

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

但是,如果一个使用 BNL 算法的 join 语句,多次扫描一个冷表,而且这个语句执行时间超过 1 秒,就会在再次扫描冷表的时候,把冷表的数据页移到 LRU 链表头部。

这种情况对应的,是冷表的数据量小于整个 Buffer Pool 的 3/8,能够完全放入 old 区域的情况。

如果这个冷表很大,就会出现另外一种情况:业务正常访问的数据页,没有机会进入 young 区域。

由于优化机制的存在,一个正常访问的数据页,要进入 young 区域,需要隔 1 秒后再次被访问到。但是,由于我们的 join 语句在循环读磁盘和淘汰内存页,进入 old 区域的数据页,很可能在 1 秒之内就被淘汰了。这样,就会导致这个 MySQL 实例的 Buffer Pool 在这段时间内,young 区域的数据页没有被合理地淘汰。

总结来说,BNL算法对系统的影响主要包括三个方面:

  • 可能会多次扫描被驱动表,占用磁盘 IO 资源;
  • 判断 join 条件需要执行 M*N 次对比(M、N 分别是两张表的行数),如果是大表就会占用非常多的 CPU 资源;
  • 可能会导致 Buffer Pool 的热数据被淘汰,影响内存命中率。

# BNL转BKA

一些情况下,我们可以直接在被驱动表上建索引,这时候就可以转为BKA算法了。

但是对于无法加索引的情况,有什么解决方案呢?

这时候,我们可以考虑使用临时表,使用临时表的大致思路如下:

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

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

  • 让表 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);

总体来说,不论是在原表上加索引,还是用有索引的临时表,我们的思路都是让 join 语句能够用上被驱动表上的索引,来触发 BKA 算法,提升查询性能。

# 参考

MySQL 实战 45 讲-极客时间 (opens new window)

#MySQL
上次更新: 2024/06/29, 15:13:44
MySQL深入30-join深入
MySQL深入32-临时表深入

← MySQL深入30-join深入 MySQL深入32-临时表深入→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式