JOIN 慢的时候,不能只看表的数量。真正决定成本的是连接顺序、驱动表行数、被驱动表有没有合适索引,以及每一轮连接要做多少次查找。

MySQL 执行 JOIN,本质上是在多张表之间组织中间结果。驱动表选错、被驱动表无法点查、过滤条件下推不充分,都会把一次连接放大成成倍扫描。

先把机制边界说清楚

JOIN 的本质是把多张表按照连接条件组合成中间结果。MySQL 会选择一个连接顺序,从驱动表取一批行,再去被驱动表查匹配行。被驱动表如果有合适索引,成本接近多次点查;没有索引,就可能退化成成倍扫描。

整体路径

JOIN:NLJ、BKA、Hash Join 怎么选

上面这张图先看粗线条:宏观上,JOIN 链路分成三层:优化器先根据统计信息决定表顺序和访问路径;执行器按嵌套循环、批量访问或哈希连接组织数据;存储引擎负责每次索引查找、范围扫描或全表扫描。连接慢,通常是这三层中的某一层估错或缺少支撑结构。

底层流程

JOIN:NLJ、BKA、Hash Join 怎么选:执行路径

底层拆解先看数据结构。「JOIN 算法」至少涉及下面几类结构:

  • 驱动表:连接循环的外层输入,行数越小越容易控制总成本。
  • 被驱动表索引:决定每一轮连接是点查、范围查还是扫描。
  • join buffer:无法有效用索引时,用来缓存外层行并减少重复扫描。
  • 哈希表:MySQL 8.0 特定场景下用于无索引等值连接的内存结构。

再看完整执行流程:

  1. 优化器枚举可能的连接顺序。
  2. 根据统计信息估算每张表访问成本和中间结果规模。
  3. 选择驱动表,并从外层表读取候选行。
  4. 对每一批外层行访问被驱动表,使用索引、join buffer 或哈希连接。
  5. 生成中间结果,继续连接下一张表或返回。

取舍与边界

版本差异上,MySQL 长期以 nested-loop join 为核心。MySQL 8.0 引入并逐步增强 hash join,8.4 文档中已经明确默认会在可行场景尽量使用 hash join;但在典型 OLTP 等值连接里,最重要的仍然是让被驱动表连接列有可用索引。

JOIN 的短板是错误会被乘法放大。驱动表多扫 1 万行,被驱动表每行再扫 1 千行,问题立刻变成千万级访问。统计信息失真、关联条件缺索引、返回列过宽、连接后再过滤,都会让中间结果膨胀。

典型问题:用机制化例子排查

JOIN 成本可以按乘法放大来看:驱动表多一批行,被驱动表缺一个索引,每一轮连接都会把成本往上推。小数据量没问题,不代表路径是稳定的。

可以落到这些动作:

  • 先看连接顺序和每张表 rows,定位乘法从哪里开始放大。
  • 确保被驱动表连接列有索引,并和过滤条件组成合适联合索引。
  • 减少连接前输入行数,不要把过滤条件都放到连接后才处理。
  • 大结果集分析型 JOIN 和 OLTP 核心查询分开治理,必要时用汇总表或搜索引擎承接。

收束:JOIN 优化是控制乘法

JOIN 优化不是少写 JOIN,而是控制连接乘法。只要驱动表够小、被驱动表访问路径稳定,多表连接可以很稳;反过来,一处缺索引就能把成本放大。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 MySQL」,欢迎关注公众号 「十三Tech」。后续会继续按机制、图解和实战排查这条线更新。

十三Tech公众号二维码