MySQL 8.0的Hash Join比嵌套循环更高效,因其将时间复杂度从O(M×N)降至接近O(M+N):先构建小表哈希表(Build阶段),再扫描大表哈希探测(Probe阶段),实现平均O(1)匹配,显著减少I/O与比较次数。

MySQL 8.0 的 Hash Join 在大数据量关联时表现卓越,根本原因不是“它更先进”,而是它把原本 O(M×N) 的暴力匹配,硬生生压到了接近 O(M+N) 的线性扫描 + 哈希查表——只要内存够、条件对,性能提升常达 5–10 倍。
Hash Join 实际执行分两步:Build 和 Probe
它不是边扫边连,而是严格分阶段:Build 阶段只扫一次小表(注意:是预估结果集体积小,不一定是行数少),构造哈希表;Probe 阶段再扫一次大表,每行算哈希值直接查表。中间没有嵌套、没有重复扫描。
- Build 阶段:对驱动表连接字段(如
t1.id)计算哈希,存整行或关键列,仅遍历一次 - Probe 阶段:对被驱动表每行的连接字段(如
t2.id)也哈希,平均 O(1) 定位匹配桶,不是逐行比对 - 若哈希表撑不住内存(超
join_buffer_size),MySQL 自动落盘分片,变成 Hybrid Hash Join,仍比嵌套循环稳定
为什么嵌套循环(NLJ)在无索引时会崩得这么快?
哪怕只是 10 万 × 50 万 行的等值连接,Nested-Loop Join 最坏要执行 5×10¹⁰ 次比较——这不是 CPU 算得慢,是磁盘 I/O 和缓冲池未命中把整个链路拖垮。
- 没索引时,NLJ 就是外层取一行 → 内层全表扫一遍,本质是 M 次独立全表扫描
- 有索引也不保险:每次点查都依赖磁盘寻道、页加载、B+树深度,数据倾斜时尤其卡顿
- 而 Hash Join 是两次顺序扫描 + 内存哈希查表,I/O 更局部,CPU cache 更友好
Hash Join 不是自动生效的,这几个硬条件缺一不可
写了 JOIN ... ON a = b 不代表就用上了 Hash Join。优化器会评估后退化成 Index Nested-Loop Join 或干脆拒绝。
- 连接条件必须是纯等值(
=),不能带函数(如ON UPPER(t1.a) = t2.b)、不能是!=或范围(>、BETWEEN) - 被驱动表连接字段**没有可用索引**——有索引时优化器大概率选 NLJ,因为建哈希表的开销可能高于几次索引点查
-
EXPLAIN ANALYZE输出里必须看到Inner hash join和Hash子节点,才是实锤启用 - 无法用
STRAIGHT_JOIN强制走 Hash Join,它只控制表顺序,不干预算法选择
别乱调 join_buffer_size,设错比不设还糟
这个参数管的是单个连接的哈希表内存上限,但它不是越大越好。
- 设太大:可能触发 OS 内存分配失败,或挤占
innodb_buffer_pool_size,反致整体吞吐下降 - 设太小:哈希表频繁溢出到磁盘,变成多轮 Probe + 文件读写,延迟陡增,
SHOW STATUS LIKE 'Handler_read%'会暴增 - 建议从默认值(通常 256KB–2MB)起步,结合
EXPLAIN ANALYZE中的hash_join_blobs_used等指标动态调优
真正决定 Hash Join 能不能跑起来的,从来不是版本号,而是你写的 SQL 是否满足等值无索引条件、驱动表是否真小、以及 join_buffer_size 是否在内存与稳定性之间卡准了那个临界点——漏掉任意一个,它就默默退回到 NLJ,你还以为是 MySQL 不给力。


















