MySQL未显示“Using join buffer (hash join)”是因为优化器仅在被驱动表无可用索引、满足等值条件、hash_join=on且内存充足时才选用Hash Join;有索引(尤其类型一致、可命中)则优先Index Nested-Loop Join。

为什么 EXPLAIN 里看不到 Using join buffer (hash join)
MySQL 优化器是否启用 Hash Join,不是看版本有没有、开关开没开,而是看被驱动表在关联字段上有没有可用索引。有索引 → 默认走 Index Nested-Loop Join(NLJ);没索引 → 才可能选 Hash Join。
常见误判点:
-
ON t1.a = t2.b中t2.b有索引,但类型和t1.a不一致(比如INTvsVARCHAR)→ 触发隐式转换 → 索引失效 → 本该走 NLJ 却走不了,但优化器仍可能因“误判有索引”而拒绝 Hash Join -
t2.b上的索引存在,但被WHERE条件过滤后选择性极差(比如重复值占 95%),优化器估算 NLJ 成本仍低于 Hash Join → 继续用 NLJ,哪怕实际更慢 - 复合索引顺序不匹配:索引是
(c1, c2),但 JOIN 条件只用c2→ 索引无法用于等值查找 → NLJ 失效,但优化器未必立刻切到 Hash Join,尤其当optimizer_search_depth较浅时
验证方式很简单:EXPLAIN FORMAT=TRADITIONAL 看 Extra 字段。没出现 Using join buffer (hash join),就说明没走。
被驱动表明明没索引,为什么还是没用 Hash Join
Hash Join 的启用还依赖几个运行时条件,缺一不可:
-
optimizer_switch中hash_join=on(默认开启,但可能被配置覆盖) - 驱动表不能太大:若驱动表行数 × 每行大小 >
join_buffer_size,会退化为分批构建哈希表,开销陡增 → 优化器可能主动弃用 - 关联字段必须是等值条件(
=),且不能带函数或表达式,例如ON t1.id = UPPER(t2.id)或ON t1.ts = DATE(t2.ts)→ 直接排除 Hash Join 路径 - 查询含
RIGHT JOIN或FULL OUTER JOIN→ Hash Join 不支持,只能回退到 NLJ 或 BNL(8.0.20+ 已移除 BNL,此时可能报错或强制改写)
典型陷阱:join_buffer_size 默认仅 256KB,一张中等宽度的驱动表几千行就溢出。这时即使被驱动表完全无索引,优化器也可能因“预估 Hash Join 内存压力过大”而跳过它。
如何强制让优化器考虑 Hash Join
MySQL 没有 USE_HASH 这类 hint(8.0.18 短暂支持后已废弃),但可通过间接手段“引导”:
- 用
IGNORE INDEX或NO_INDEX提示禁用被驱动表上的关联索引,例如:SELECT /<em>+ NO_INDEX(t2 idx_on_b) </em>/ * FROM t1 JOIN t2 ON t1.a = t2.b - 删除或临时禁用被驱动表上“看似可用但实际低效”的索引(比如高重复率的普通索引),让优化器无索引可选
- 调大
join_buffer_size(需会话级 SET,且注意全局内存压力),降低 Hash Join 的“内存风险评分” - 对小表显式指定为驱动表:
STRAIGHT_JOIN+ 把小表放前面,配合无索引的大表作为被驱动表,提高 Hash Join 入选概率
注意:STRAIGHT_JOIN 只控制连接顺序,不保证算法;最终是否 Hash Join,仍由优化器基于成本模型决定。
LEFT JOIN 场景下 Hash Join 的隐藏风险
MySQL 8.0.18–8.0.20260430 版本中,Hash Join 在多表 LEFT JOIN 中存在已知缺陷:当 join_buffer_size 不足导致哈希表溢出(spill to disk)时,NULL 行可能丢失,结果集行数错误。
触发链路:
- 多表 LEFT JOIN → 优化器选 Hash Join 作为某一步连接方式
- 驱动表数据量超
join_buffer_size→ 哈希表分批构建并落盘 - 后续 probe 阶段反复加载/存储中间 record →
EQRefIterator缓存被破坏 → 左表本应补 NULL 的行消失
这不是性能问题,是正确性问题。如果你的业务依赖 LEFT JOIN 的语义完整性(比如报表、ETL),且 MySQL 版本在 20260430 之前,务必检查 EXPLAIN 输出是否含 Using join buffer (hash join) —— 出现即需警惕,建议用 SET optimizer_switch='hash_join=off' 临时规避。


















