怎样看懂MySQL 8.0执行计划中新增的Hash Join匹配过程?

作者:袖梨 2026-07-16
EXPLAIN FORMAT=TREE是唯一能直观显示Hash Join构建表、探测表及连接键的命令,其树状结构中“Inner hash join (t2.c1 = t1.c1)”明确标识探测表字段在左、构建表字段在右,带Hash标签的子节点为构建表扫描,另一支为探测表扫描;EXPLAIN ANALYZE则补充实际扫描行数、build/probe耗时及disk overflow标记,可验证内存是否充足、驱动表选择是否合理。

EXPLAIN FORMAT=TREE 是唯一能直观看到 Hash Join 匹配过程的命令,其他格式(如 EXPLAIN 默认或 EXPLAIN FORMAT=TRADITIONAL)只会显示 Using join buffer (hash join) 这类模糊提示,无法反映构建表、探测表、哈希键等关键结构。

怎么看懂 EXPLAIN FORMAT=TREE 输出里的 Hash Join 节点

输出是嵌套树状结构,每层缩进代表执行层级。Hash Join 节点本身会明确写出 Inner hash joinLeft hash join,并标注连接键,例如:Inner hash join (t2.c1 = t1.c1)

  • 等号左边的字段(t2.c1)是探测表(被驱动表)的连接列
  • 等号右边的字段(t1.c1)是构建表(驱动表)的连接列
  • 其子节点中带 Hash 标签的那一支,就是构建阶段扫描的表(如 Table scan on t1
  • 另一支不带 Hash 的,就是探测阶段扫描的表(如 Table scan on t2
  • 如果出现多层 hash join 嵌套(比如三表 JOIN),说明 MySQL 拆成了多个两表 Hash Join 步骤,从右向左或按代价估算顺序执行

EXPLAIN ANALYZE 能补全哪些关键信息?

EXPLAIN ANALYZE 会在实际执行 SQL 后返回运行时统计,比 FORMAT=TREE 多出三类关键数据:

  • 每张表实际扫描行数(rows 字段),可验证优化器是否真选了小表做构建表
  • 哈希表构建耗时(build time)和探测耗时(probe time),差距大说明内存不足触发了磁盘溢出
  • 是否发生 disk overflow —— 若出现该字样,说明构建表超出 join_buffer_size,已分块写入磁盘,性能必然下降

为什么有时候明明没索引,却没走 Hash Join?

常见原因不是语法错,而是优化器“绕过”了它:

  • JOIN 条件里混入了 ORLIKE、函数表达式(如 UPPER(t1.name) = t2.name),哪怕只有一处,整个 ON 子句就不再满足纯等值条件
  • 存在可用的单表索引(例如 WHERE t1.id = ? 有主键索引),优化器倾向用 Index Nested-Loop Join,即使连接列本身无索引
  • 表统计信息陈旧,ANALYZE TABLE 没执行过,导致优化器误判构建表大小,选错驱动方
  • MySQL 8.0.18–8.0.19 允许用 SET optimizer_switch='hash_join=off' 关闭,但 8.0.20+ 该开关已失效,无需检查

如何稳定触发并验证 Hash Join 生效?

最可靠的方式不是猜配置,而是控制输入结构:

  • 用派生表强制“断开”原始表与索引的关联:SELECT * FROM (SELECT * FROM t1 WHERE ... ) AS a JOIN (SELECT * FROM t2 WHERE ... ) AS b ON a.c1 = b.c1
  • 确保 ON 中只有简单等号,且两边字段均为基础列(非表达式、非 NULL 安全比较
  • 执行前先 ANALYZE TABLE t1, t2 更新统计信息
  • 必须用 EXPLAIN FORMAT=TREE 看树形结构,再用 EXPLAIN ANALYZE 看真实耗时与溢出标记

真正容易被忽略的是:Hash Join 的性能优势高度依赖构建表能否全量驻留内存。一旦触发磁盘溢出,I/O 开销可能反超 Nested-Loop —— 所以 join_buffer_size 不是越大越好,而是要略大于预估的构建表字节大小(注意是字节数,不是行数)。

相关文章

精彩推荐