哈希连接比嵌套循环快的核心是将时间复杂度从o(m×n)降至接近o(m+n):先构建小表哈希表(build阶段),再扫描大表哈希探测(probe阶段),实现近o(1)匹配,显著减少i/o与比较次数。

哈希连接在多数等值连接场景下比嵌套循环快,核心原因是把 O(M×N) 的暴力匹配降到了接近 O(M+N) 的线性扫描 + 哈希查表。
哈希连接的执行阶段拆解
哈希连接不是“同时扫两张表”,而是分两步:先构建(Build),再探测(Probe)。
- Build 阶段:选小表(注意是 内存占用更小,不一定是行数少)作为驱动表,对连接字段(如
t1.a)计算哈希值,把整行或关键列存入内存哈希表。这个过程只遍历一次驱动表。 - Probe 阶段:遍历被驱动表(大表),对每行的连接字段(如
t2.a)也计算哈希值,直接查哈希表找匹配项——平均时间复杂度是 O(1),不是逐行比对。 - 如果哈希表建不进内存(超出
join_buffer_size),MySQL 会落盘分片处理,性能下降但依然比嵌套循环全表扫描稳定。
嵌套循环为什么慢?它真在“嵌套”
嵌套循环(NLJ)本质是双层 for 循环:外层取一行,内层全表扫一遍匹配。即使加了索引,也可能因驱动表选错、索引未命中或数据分布倾斜导致大量随机 I/O。
- 假设
t1有 10 万行,t2有 50 万行,且连接字段无索引:NLJ 最坏要执行 10⁵ × 5×10⁵ = 5×10¹⁰ 次比较。 - 哪怕
t2有索引,每次从t1取一行都要回表或走索引查找,实际是 10⁵ 次独立的点查,受磁盘寻道、缓冲池命中率影响极大。 - 而哈希连接只要两次顺序扫描 + 一次哈希查表,I/O 更局部,CPU cache 更友好。
Hash Join生效的关键前提不能漏
MySQL 不是“写了 JOIN 就自动用哈希”,它得满足几个硬性条件,否则还是会退化成 NLJ 或 BNL(8.0.20+ 已移除 BNL):
- 连接条件必须是等值(
ON t1.a = t2.b),不支持!=、LIKE、函数包裹字段(如ON UPPER(t1.a) = t2.b)。 - 驱动表选择由优化器决定,但倾向选 预估体积更小 的表;你无法用
STRAIGHT_JOIN强制哈希连接,它只控制表顺序,不控制算法。 - 若连接字段上有可用索引,优化器大概率仍选 NLJ——因为索引点查可能比建哈希表更快,尤其当小表本身就很小时。
- 可通过
EXPLAIN ANALYZE确认是否真用了哈希:输出里出现Inner hash join和Hash子节点才是实锤。
别盲目调大 join_buffer_size
哈希表默认在内存里建,大小受 join_buffer_size 控制(每个连接独享)。但它不是越大越好:
- 设太大可能触发操作系统内存分配失败,或挤占 InnoDB buffer pool,反而拖慢整体查询。
- 设太小会导致哈希表溢出到磁盘,变成多轮 Probe + 文件读写,延迟陡增——这时看
SHOW STATUS LIKE 'Handler_write%'能发现异常写操作。 - 建议从默认值(如 256KB)起步,结合
EXPLAIN ANALYZE中的actual time和loops观察,只在确认哈希溢出时微调。
真正容易被忽略的是:哈希连接的“高效”依赖于驱动表能基本装进内存。如果两张表都超大、连接字段又没索引,即使 MySQL 8.0.20+ 强制用哈希,也会频繁落盘,此时不如先加索引或拆分查询。算法再新,也救不了数据和设计层面的根本瓶颈。











