Mysql杂志(三十一)——Join连接算法与子查询、排序优化

Join连接算法

我们之间也说过join其实就是一次又一次的循环对比,A连接B,那么就是先找到A表所以复合条件的行数据,然后一条一条的循环扫描满足条件的B数据,最后输出结果。先看看这几个算法的对比表格:

算法类型​ ​引入版本​ ​核心机制​ ​最佳场景​ ​触发条件​
​Nested Loop Join​ 全版本 双重循环匹配 中小表关联 默认算法
​Block Nested Loop​ 全版本 缓冲块优化 无索引连接 join_buffer_size足够
​Hash Join​ 8.0+ 哈希表加速 大表等值连接 无合适索引+等值条件
​Batched Key Access​ 5.6+ 批量键访问 二级索引回表 mrr=on

1. 嵌套循环连接 (Nested Loop Join)

这个其实就是开头说的那种,也是mysql中最简单而且是默认的算法,其实这个算法也分两种一个是Simple Nested Loop Join,一个是Index Nested Loop Join,前者就是不使用索引的简单循环算法,假设A有100条,B也有100条,那么总共的成本就是100*100=1w,所以查询的成本其实是非常高的,而第二种就是使用索引,通过聚餐索引或者二级索引直接定位满足条件的行,性能会高很多。

2. 块嵌套循环连接 (Block Nested Loop)

这个其实就是把被驱动表的数据加载到内存块中(没有索引的情况下),本来A的数据扫描B需要加载到内存中,而且是扫描一次就加载一次很浪费资源,而这个就是直接把B表的一部分加载到内存中,然后进行循环匹配,不合适的就加载下一部分内存,下次循环还是会使用这个内存块,不会重复加载,但是这个是受到缓存块大小的限制,而且mysql8.2被Hash join替代了这个算法。

3. 哈希连接 (Hash Join)

这个就是为了替代Block Nested Loop而产生的算法,这个的限制就是必须是等值查询,而且在没有索引的情况下,不支持 LIKE, >, <等范围条件,而且性能不足的情况下性能会显著下降,因为哈希表的特性就是找对应的桶,或者叫数据指针,这个就是为被驱动表建立哈希表,然后循环查找的时候就查询这个哈希表。这个是对比表格:

对比维度​ ​Hash Join​ ​Block Nested Loop (BNL)​​
​算法类型​ 哈希表查找 块嵌套循环
​引入版本​ MySQL 8.0+ 全版本支持
​连接条件​ 仅支持等值连接(=) 支持所有连接条件
​内存使用​ 高(需构建哈希表) 中(join_buffer缓存)
​执行阶段​ 构建阶段+探测阶段 单阶段块扫描
​最佳场景​ 大表等值连接 中小表无索引连接
​I/O特点​ 随机I/O为主 顺序I/O为主

4. 批量键访问 (Batched Key Access)

批量键访问 (Batched Key Access, BKA) 是 MySQL 针对二级索引回表查询的优化技术,通过将随机 I/O 转换为顺序 I/O 提升性能。这个是开启的条件:

条件类型​ ​具体要求​ ​检查方法​
系统配置 optimizer_switch='batched_key_access=on' SHOW VARIABLES
查询特征 使用二级索引且需要回表 EXPLAIN显示 Using index condition
版本要求 MySQL 5.6+ SELECT VERSION()

这个是优点:

优点​ ​影响程度​ ​产生效果​
减少随机I/O ★★★★★ 大幅降低延迟
提高缓存命中 ★★★★ 减少物理读
批量处理 ★★★ 降低系统调用
预读优化 ★★ 利用磁盘特性

这个是缺点:

缺点​ ​影响程度​ ​缓解方案​
内存消耗 ★★★ 调整join_buffer_size
额外排序 ★★ 确保mrr_cost_based=off
冷数据效果差 ★★ 预热缓存
版本限制 ★ 升级到5.6+

简单来说这个其实是搭配Nested Loop Join使用的,本来使用二级索引需要回表的,而且每找到一次就要回表一次,使用这个后会把需要回表的索引记录到内存缓存中,然后一次性回表,把大量的随机IO变成了一次的顺序IO性能提升还是很明显的,而且Mysql默认是开启的。

其实总结起来就是一句话,小表驱动大表,小结果集驱动大结果集,因为小结果驱动代表的就是减少循环的次数。

子查询优化

子查询大家应该都不陌生,子查询是mysql4的时候引入的一个功能,目的就是为了帮助我们完成更加复杂的业务场景,但是子查询其实本质上来说性能并不好,因为子查询会生成一个临时表,我们都知道无法给临时表添加索引(当然主包说的是隐式的临时表,我们显示声明的还是可以加索引的),那么如果一个表的数据集特别大,然后子查询又是select* 这种全字段,然后我们的主表再使用这个子查询的某个字段查询的时候,性能就是非常糟糕的,一个是没有索引,一个就是数据量特别大,而且在生成和最后删除临时表的时候,也是非常消耗系统资源的。所以我们需要对一些需要使用子查询的场景进行优化,从而提高sql的性能。

1. 子查询转为JOIN(最常用)

这个其实很好理解,因为一个是可以使用被驱动表的索引,一个是循环对比被驱动表的行数据,最后形成一个结果集,并不会产生一个中间的临时表,也就不存在大量的系统资源浪费了。

2.派生表条件下推(5.7+)以及减少列数量

这个其实也很好理解,把过滤条件下推到子查询中,不要在外层查询中再过滤,因为这样会降低临时表产生的数据集,从而导致性能提高,另外不要使用select * 只返回需要的字段,这样性能也会提高。

sql 复制代码
-- 低效写法
SELECT * FROM (
    SELECT * FROM orders WHERE status = 'completed'
) AS t WHERE t.amount > 1000;

-- 优化写法(条件下推)
SELECT * FROM (
    SELECT * FROM orders WHERE status = 'completed' AND amount > 1000
) AS t;

3.EXISTS重写​

这个其实在mysql中会被优化成半连接,也就是只保留true的驱动表行数据,为什么性能会提高其实是上面说过,转Join一样。

排序优化

1.使用limit

其实我们在进行排序的时候,就算有这个字段的索引,也可以会失效,因为二级索引是需要回表的,而且我们也知道Mysql优化器是以成本最低为前提的,假如使用索引比不使用索引成本更高,肯定就不会使用索引,就是说多个列的组合索引全部命中的情况下,所以使用limit进行分页的话,这样就会降低回表的数量集,所以使用索引的可能性会高。

2.索引顺序

这个就很好理解了,我们知道索引是有升序和降序的,默认是升序的索引,假如我们对一个字段进行降序排序,那么肯定就不会使用到索引的,我们需要更加业务情况添加降序索引。

3.文件排序算法------单路排序、双路排序

其实我们大部分的场景使用的都是文件排序,也就是把数据加载到内存,然后再进行排序,之前我们说降序排序的时候就知道了,文件排序是很消耗系统资源的,但是这个也是没办法的,因为我们不可能什么情况下都加索引对不对。所以我们有需要了解一下什么是单路排序什么是双路排序。

单路排序:也就是一次性把满足条件的数据全部加载到内存然后进行排序,缺点就是消耗内存(sort_buffer_size),优点就是比双路快,5.7以上版本默认这个算法。

双路排序:就是先把排序列和主键加载到内存排序,然后再批量回表获取数据,缺点就是2次IO会慢,但是消耗的内存会小很多。

总结

本篇主要讲的就是Join的连接算法以及子查询和排序优化的一些内容。

相关推荐
IT19955 小时前
Fail2Ban 防护 MySQL 实战|登录失败 3 次,IP 封禁 24 小时
数据库·mysql
夕除5 小时前
redis--IO 多路复用(select + poll)
数据库·sql
paopaokaka_luck8 小时前
流浪动物救助与收养系统(DeepSeek AI对话、 ECharts多维分析、救助流程衔接、领养与回访衔接、 捐赠确认与公示、志愿者协作)
java·前端·javascript·人工智能·mysql·echarts·推荐算法
lzhdim9 小时前
100条常用SQL语句
数据库·sql·mysql·mybatis
天河归来9 小时前
从原始消息到业务数据:三层数据架构的设计与实践
数据库·数据仓库·架构
数据库小学妹10 小时前
金融数据库成本怎么算?五笔钱加一张TCO表
数据库·金融行业·数据库选型·国产化替代·tco·金融数据库
KaiwuDB10 小时前
KaiwuDB 分布式集群扩缩容,从原理到完整操作踩坑总结
运维·数据库·分布式
auspicious航10 小时前
PostgreSQL的备份与恢复
数据库·postgresql
这个DBA有点耶10 小时前
从 MySQL 到时序数据库:我帮朋友做迁移的完整记录
数据库·架构·dba
泡海椒11 小时前
深度解析JQuick-SQL AST到逻辑计划转换机制|分布式SQL原理
数据库·分布式·sql