MySQL 8.0.20移除了Block Nested Loop,之前学的JOIN优化知识还适用吗?

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

"小表驱动大表"------这句话你肯定听过。

但上周一个朋友发来一条JOIN查询,两张表:orders表200万行,users表50万行。他说:"users表小,应该是驱动表吧?为什么EXPLAIN显示orders是驱动表,查询还跑了8秒?"

我看了眼他的SQL:

bash 复制代码
SELECT o.order_id, u.username, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.create_time >= '2026-09-01'
  AND u.status = 'ACTIVE';

我问他:"orders表200万行,加了create_time条件后剩多少行?users表50万行,加了status条件后剩多少行?"

他愣了一下,查了一下:orders过滤后只剩3000行,users过滤后还有40万行。

驱动表选的是orders,没选错。

"小表驱动大表"里的"小表",看的是WHERE条件过滤后的结果集,不是表的总行数。这个误解,我在技术群里见过太多次了。

今天把驱动表选择和Join Buffer这件事彻底拆开讲清楚。

一、驱动表到底怎么选的?

JOIN的本质是嵌套循环:从一张表循环取出数据,拿着关联字段去另一张表中匹配。

  • 驱动表:外层循环的表,是数据遍历的起点

  • 被驱动表:内层循环的表,是每次循环匹配的对象

优化器选择驱动表的核心逻辑是:经过WHERE条件过滤后,结果集行数最少的表作为驱动表

注意,这里的关键词是"过滤后"。一张200万行的表和一张50万行的表,谁当驱动表,取决于WHERE条件把哪张表过滤得更小。

不同JOIN类型的驱动表选择规则:

JOIN类型 驱动表选择规则
INNER JOIN 优化器根据过滤后结果集大小自动选择
LEFT JOIN 左表为驱动表(除非WHERE条件强制过滤右表,可能转为INNER JOIN)
RIGHT JOIN 右表为驱动表(同理)

一个关键细节:LEFT JOIN的左表默认是驱动表,但如果WHERE条件中包含了对右表的强制过滤条件,优化器可能将LEFT JOIN转为INNER JOIN,重新选择驱动表。这一点很多人不知道。

那怎么确认优化器选了谁当驱动表?看EXPLAIN输出:id值相同的行,table列排在上面的就是驱动表

bash 复制代码
EXPLAIN SELECT o.order_id, u.username
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.create_time >= '2026-09-01' AND u.status = 'ACTIVE';

输出中,如果ordersidusers一样,但orders排在users上面,说明orders是驱动表。

二、Join Buffer是什么?

当被驱动表的关联字段没有索引时,MySQL无法使用Index Nested Loop(索引嵌套循环连接)。这时候,如果每取一行驱动表的数据就去全表扫描一次被驱动表,效率会极低。

Join Buffer的引入就是为了解决这个问题。

它的工作方式是:把驱动表的一批数据先缓存到内存中,然后一次性拿这批数据去匹配被驱动表,减少被驱动表的全表扫描次数。

举个例子:驱动表有1000行,被驱动表有100万行。没有Join Buffer,需要扫描被驱动表1000次。有了Join Buffer,假设每次缓存100行驱动表数据,只需要扫描被驱动表10次。扫描次数从1000次降到10次,提升100倍。

但有一个关键细节很多人不知道 :Join Buffer缓存的是查询列表中所有需要的列 ,不只是关联字段。这意味着SELECT *会让Join Buffer占用大量内存,更快触发溢出。只查询必要的列,能显著提升Join Buffer的利用效率。

join_buffer_size默认值只有256KB,最大可设置为4GB。 但这个参数的调整需要谨慎。

三、MySQL 8.0.20的重大变化:Block Nested Loop被移除

这是本文最重要的一部分。

在MySQL 8.0.20之前,无索引的JOIN使用Block Nested Loop(BNL) 算法,Join Buffer是BNL的核心组件。

从MySQL 8.0.20开始,BNL算法被正式移除,Hash Join成为无索引JOIN的唯一算法

这意味着什么?

Hash Join不使用Join Buffer。

Hash Join的工作方式是:选择结果集较小的表作为构建表(build table),在内存中构建哈希表;然后用另一张表(探测表)的每一行去哈希表中探测匹配。

哈希表的大小由join_buffer_size控制,但和BNL的用法完全不同。

  • BNL的Join Buffer:缓存驱动表的数据行,用于减少被驱动表的扫描次数

  • Hash Join的哈希表:存储构建表的关联键和需要的列,用于O(1)探测

这是两个完全不同的概念。在MySQL 8.0.20+的环境中,join_buffer_size的作用对象已经从"Join Buffer"变成了"哈希表"。

版本差异总结:

MySQL版本 无索引JOIN算法 join_buffer_size的作用
5.7及以下 Block Nested Loop 缓存驱动表数据行
8.0.18-8.0.19 Hash Join(实验性) 哈希表大小
8.0.20+ Hash Join(唯一) 哈希表大小

四、Join Buffer(哈希表)调优实战

调优原则一:先确认是否真的需要调大

在MySQL 8.0.20+中,如果EXPLAIN显示Using join buffer (hash join),说明优化器选择了Hash Join。但哈希表不一定需要很大------如果构建表的结果集本身就很小(比如几千行),默认的256KB可能就够了。

只有当构建表结果集较大、哈希表溢出到磁盘时,才需要调大。

调优原则二:单连接不要超过1MB

join_buffer_size每个连接独立分配的。如果有100个并发连接,每个连接的哈希表都是1MB,总内存消耗就是100MB。调得太大,高并发场景下容易OOM。

生产环境建议:单连接不超过1MB,全局不超过10MB。对于OLTP场景,保持较小值(256KB-512KB)更安全;批处理类场景可以适当增大。

调优原则三:优先加索引,而不是调参数

最根本的优化是让被驱动表的关联字段有索引。有索引时,JOIN走Index Nested Loop,不需要Hash Join,也就不需要大哈希表。

索引是JOIN优化的第一原则,参数调优只是权宜之计。

五、小结

JOIN优化的核心不在于调大join_buffer_size,而在于两件事:一是让优化器选对驱动表,二是让被驱动表的关联字段有索引。驱动表看的是过滤后结果集,不是表的总行数。MySQL 8.0.20之后Block Nested Loop被移除,Hash Join成为无索引JOIN的唯一算法,join_buffer_size的作用也从"缓存驱动表数据"变成了"控制哈希表大小"。理解这些底层变化,才能写出真正高效的JOIN查询。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
陆柒1452 小时前
从洋葱模型到 Elpis Core:我对 Node.js 服务端开发的理解
架构
懂软件的胡子个哥2 小时前
微信 API 消息回调怎么设计,才能避免丢消息和重复处理
运维·微信·架构·wechatapi·个人微信号二次开发
吉甫作诵2 小时前
Redis 常用命令大全:11 大类命令速查手册
运维·数据库·redis·缓存·nosql
2501_933670792 小时前
库存管理分析岗校招能力模型:SQL、库存周转、补货预测怎么准备
数据库
属于自己的天空3 小时前
不用再手动查表结构了:配好 MCP,Claude Code 自己读数据库生成代码
数据库·后端
Y3815326623 小时前
SERP 数据清洗实战:字段标准化、日期解析与去重键
开发语言·数据库·python·python数据库
Databend3 小时前
Databend 原生数据血缘:追溯指标来源,检查变更影响
大数据·数据库·sql
镜舟科技4 小时前
Semantic View 技术解析(二):业务口径如何进入数据库执行路径
数据库·sql·agent
TechWJ4 小时前
没有公网 IP 也想远程连 PostgreSQL?从本地数据库到固定 TCP 地址完整配置
大数据·数据库·网络安全·postgresql·内网穿透