为什么你的JOIN查询慢?驱动表选对了,但被驱动表没索引

简介: MySQL的JOIN优化中,驱动表的选择直接决定执行效率。“小表驱动大表”这句口诀几乎人人都会背,但真正理解其底层逻辑的人不多——驱动表看的是过滤后结果集,不是表的总行数;Hash Join引入后,Join Buffer的角色也发生了根本变化。本文从驱动表选择逻辑、Join Buffer工作机制、MySQL 8.0.20之后的算法演进三个层面,拆解多表JOIN的底层原理与实战调优方法。

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

“小表驱动大表”——这句话你肯定听过。

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

我看了眼他的SQL:

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列排在上面的就是驱动表

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 不愁

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

相关文章
|
15小时前
|
新能源 API
VIN 码车辆查询‑车架号解析‑车辆原厂参数获取‑VIN 码车型识别 API 接口介绍
VIN码车辆查询接口可一键解析17位车架号,精准还原品牌、年款、发动机、排放标准等百余项原厂参数,支持乘用车/商用车/新能源及国产/进口车型,覆盖率超95%,免自建字典库,为汽车业务提供权威结构化档案。
33 0
|
7月前
|
人工智能 监控 测试技术
AI 应用软件的开发流程
2026年AI开发已升级为数据驱动、模型导向、高度自动化的AI-SDLC流程,涵盖需求评估、数据准备、模型开发(Prompt/RAG或微调)、架构集成、概率化测试及持续监控六大环节,强调不确定性管理与成本效能平衡。
|
4月前
|
SQL 关系型数据库 MySQL
批量操作性能飙升:从30秒到1秒的三种实战方法
业务系统中经常需要批量导入或更新大量数据(如Excel上传、定时同步)。许多开发人员采用循环单条执行的方式,导致1万条数据耗时30秒以上,严重影响用户体验。本文从数据库IO、事务开销、锁竞争三个角度分析单条操作的性能瓶颈,并给出三种优化方案:批量INSERT、LOAD DATA文件导入、批量UPDATE用临时表。每种方案均附实测数据对比与适用场景说明,帮助读者在1万\~100万行级别批量操作中选择最优策略。
|
1月前
|
SQL 关系型数据库 MySQL
UNION vs UNION ALL:一个“ALL”字,性能差了一个数量级
UNION和UNION ALL的区别很多人知道,但INTERSECT和EXCEPT的执行机制、性能差异,以及如何用JOIN和子查询替代,很多人并不清楚。本文从集合操作的执行计划出发,拆解UNION去重的“隐形代价”、INTERSECT与INNER JOIN的本质差异、EXCEPT与NOT EXISTS的性能对比,并通过真实案例展示集合操作在业务场景中的正确用法与避坑指南,帮助读者从“会写集合操作”升级到“理解集合操作的底层逻辑”。
|
3月前
|
人工智能 Cloud Native 关系型数据库
MySQL 8.4 LTS来了!从8.0到8.4,DBA必须知道的5个核心变化
MySQL 8.0社区版将于2026年结束生命周期,8.4 LTS作为首个长期支持版本,提供5年超长支持周期(至2031年)。本文从InnoDB并行查询、Redo Log动态容量、默认认证插件变更、参数默认值调整、云原生适配五个维度,梳理DBA升级前必须掌握的核心变化,并提供升级检查清单。
|
7月前
|
数据处理 数据格式
在Excel中一次性粘贴多列数据时选择性粘贴特定格式
在Excel中一次性粘贴多列数据时选择性粘贴特定格式
912 2
|
6月前
|
前端开发 JavaScript Java
企业级全HIS源码支持二次开发 | 三级医院标准
HIS系统解决方案:告别传统闭源医疗系统的束缚,提供100%纯净源码,支持无限二次开发。覆盖门诊挂号、电子病历等核心模块,标准化API轻松对接第三方系统。采用Java/SpringBoot微服务架构和Vue.js前端,确保高并发性能和数据安全。帮助医疗机构实现自主可控的数字化升级,大幅降低开发成本。
|
7月前
|
存储 安全 Linux
Linux sed命令详细教程
sed是Linux下高效流编辑器,GNU sed为其主流实现。它单次遍历输入,支持管道过滤、批量配置修改与日志处理,具备无交互、原地编辑、扩展正则等优势,是Shell自动化必备利器。
|
3月前
|
SQL Oracle 容灾
数据库迁移后的“数据一致性”到底怎么验?
本文聚焦数据迁移后如何科学验证一致性——详解全量、增量、抽样三类校验方法,对比pt-table-checksum等工具优劣,并给出分阶段落地流程与避坑指南。
|
7月前
|
数据可视化 Linux 网络安全
linux服务器的文件/文件夹的可视化管理方法
linux服务器有可能是你本地内网的服务器,也有可能是云端的服务器,因此,linux的网络你有可能直接能连接,也有可能无法直接连接,比如在云端内网的服务器,就不可能每一台都直接开放ssh端口。那么,不管是能不能直接开放端口的linux服务器,它里面的文件/文件夹用什么可视化工具来管理呢? 面对网络不可知的情况,可以使用yunedit-ssh来管理linux的文件/文件夹,因为yunedit-ssh有ssh隧道功能,即使linux服务器在云端的内网,也可以使用yunedit-ssh的ssh隧道功能,先将内网服务器的22端口,映射到本地,然后再连接本地端口即可连接上内网的服务器。