mysql 8.0 默认启用 hash_join=on 可能导致关联查询退化,尤其当驱动表过大(超约10万行)时,哈希构建耗内存、触发磁盘写入,性能反不如bnl;根本原因多为被驱动表缺索引、驱动表结果集未过滤、字符集不一致等底层缺陷,hash_join仅暴露而非制造问题。

MySQL 8.0 默认启用 hash_join 导致关联查询退化
不是所有 JOIN 都适合哈希连接。MySQL 8.0 默认开启 hash_join=on,对小结果集的等值 JOIN(如 JOIN ... ON a.id = b.user_id)可能提速,但一旦驱动表返回行数超阈值(默认约 10 万行),哈希构建阶段会大量消耗内存、触发临时磁盘写入,反而比 Nested Loop 更慢。你看到的“极慢”,大概率是执行计划从 Using join buffer (Block Nested Loop) 变成了 Using where; Using join buffer (Hash Join),且 Extra 中出现 Using temporary。
用 hash_join_seed 快速验证是否为哈希连接引发的问题
hash_join_seed 是 MySQL 8.0.20+ 引入的调试参数,它不改变逻辑,只控制哈希函数的随机种子——相同 seed 下,哈希分布一致;不同 seed 下,可能触发不同执行路径(比如 fallback 到 BNL)。这不是调优参数,而是诊断开关:
- 临时禁用哈希连接:执行
SET SESSION optimizer_switch='hash_join=off';,再跑慢 SQL,看是否恢复到原来的速度 - 强制切换哈希行为:执行
SET SESSION hash_join_seed = 12345;,再执行同一条 SQL,对比EXPLAIN FORMAT=TREE输出中是否跳过 Hash Join 节点 - 注意:该变量仅对当前 session 生效,且不能在事务中动态修改后立即生效——需在 BEGIN 前设置
真正要改的不是 seed,而是索引和驱动顺序
靠 hash_join_seed 或关掉 hash_join 只是掩盖问题。根本原因是旧查询依赖 BNL 的“容忍性”,而哈希连接暴露了底层缺陷:
- 被驱动表缺少关联字段索引:例如
orders JOIN users ON orders.user_id = users.id,但users.id没有主键或索引 → 哈希连接无法快速探测,退化为全表扫描 - 驱动表结果集过大:
WHERE条件未下推,导致 JOIN 前就扫描百万行 → 应优先加WHERE过滤,或把高过滤度表放在 LEFT - 字符集/校对规则不一致:如一表用
utf8mb4_0900_as_cs,另一表用utf8_general_ci,会导致隐式转换,索引失效,哈希连接也救不了
查执行计划时重点看 rows_examined 是否暴涨,以及 key 列是否为 NULL —— 这比纠结 seed 值更直接。
别忽略 optimizer_switch 的连锁影响
hash_join 很少单独作祟。它常和 derived_merge=on、skip_scan=on 一起激活,共同改变子查询展开和范围扫描逻辑。一个典型陷阱是:SELECT * FROM (SELECT ... FROM t1 WHERE x > ?) dt JOIN t2 ON dt.id = t2.ref,在 8.0 中可能被合并为单层查询,却因统计信息不准选错驱动表。
- 检查当前生效值:
SELECT @@session.optimizer_switch; - 最小化复现:用
SET SESSION optimizer_switch='hash_join=off,derived_merge=off,skip_scan=off';逐个关闭,定位主因 - 注意:某些开关(如
semijoin)关闭后可能让 IN 子查询变回 N+1 查询,得权衡
真正棘手的不是哈希连接本身,而是它把过去被 BNL “吞掉”的低效设计彻底暴露出来——比如没走索引的 ON 条件、没加 LIMIT 的子查询、跨字符集关联。seed 只是探针,不是解药。











