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的连接算法以及子查询和排序优化的一些内容。

相关推荐
程序新视界3 小时前
在连表查询场景下,MySQL隐式转换存在的坑
数据库·mysql·dba
九河云4 小时前
在云计算环境中实施有效的数据安全策略
大数据·网络·数据库·云计算
咋吃都不胖lyh4 小时前
MySQL 与Power BI 的作用,以及在数据分析中扮演的角色
mysql·数据分析·powerbi
爱吃烤鸡翅的酸菜鱼4 小时前
从数据库直连到缓存预热:城市列表查询的性能优化全流程
java·数据库·后端·spring·个人开发
dualven_in_csdn5 小时前
ubuntu离线安装 xl2tpd
linux·数据库·ubuntu
初听于你7 小时前
高频面试题解析:算法到数据库全攻略
数据库·算法
瓯雅爱分享11 小时前
Java+Vue构建的采购招投标一体化管理系统,集成招标计划、投标审核、在线竞价、中标公示及合同跟踪功能,附完整源码,助力企业实现采购全流程自动化与规范化
java·mysql·vue·软件工程·源代码管理
BTU_YC13 小时前
Neo4j查询计划完全指南:读懂数据库的“执行蓝图“
数据库·neo4j