Hash Join在MySQL 8.0.20+中默认替代BNL,因其将O(M×N)降为O(M+N),避免被驱动表重复全表扫描与IO,大幅提升无索引关联性能。

Hash Join 在 MySQL 8.0.20+ 中默认替代 BNL,核心原因是它把 O(M×N) 的逐块比对,降为接近 O(M+N) 的哈希探测,且大幅减少被驱动表的重复 IO。
BNL 的实际执行开销在哪
BNL 本质仍是“驱动表分批 → 被驱动表反复全扫”,哪怕用了 join_buffer_size 缓冲,只要被驱动表没索引,它就得反复读磁盘:
- 假设驱动表过滤后剩 5000 行,
join_buffer一次能装 100 行,则被驱动表要被扫描 50 次 - 每次扫描都触发一次全表 IO(哪怕有部分页在 buffer pool,仍需大量随机访问)
- 匹配逻辑是逐字段比较,无预筛选,CPU 和内存带宽压力都高
-
EXPLAIN中看到Using join buffer (Block Nested Loop)就是它在干活
Hash Join 怎么省掉重复扫描
它把驱动表数据转成内存哈希表(key 是关联字段值,value 是整行或必要列),被驱动表只扫一遍,每行算一次 hash 值去查表:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 建哈希表过程:单次遍历驱动表,构造 hash map,时间复杂度 O(M)
- 探测过程:单次遍历被驱动表,每行计算 hash → 查表 → 匹配 → 输出,时间复杂度 O(N)
- 真正避免了“被驱动表被扫几十遍”的瓶颈,尤其当被驱动表大、IO 昂贵时优势明显
-
EXPLAIN中出现Using join buffer (hash join)才表示启用成功
为什么不是所有 JOIN 都自动走 Hash Join
MySQL 优化器会权衡内存、数据分布和连接类型,以下情况会绕过 Hash Join:
- 驱动表太大,超出
join_buffer_size且无法分批建哈希表(此时可能退化为磁盘 hash 或改用 NLJ) - 关联条件含非等值操作(如
、<code>BETWEEN),但注意:MySQL 8.0.20+ 已支持 non-equi-join 的 hash 变体,仅限特定场景 - 被驱动表关联字段**有可用索引**,优化器大概率选
Index Nested-Loop Join(INLJ),因为索引查找更稳定、内存开销更低 - LEFT/RIGHT JOIN 中,若驱动表是右/左表,且无法保证哈希构建完整性,也可能抑制 hash join
实操中怎么确认并干预 Hash Join 是否生效
别只看 SQL 写法,得看执行计划和运行时行为:
- 执行
EXPLAIN FORMAT=TRADITIONAL或EXPLAIN ANALYZE,重点看Extra列是否含hash join - 检查
join_buffer_size是否足够:太小会导致 hash 表被迫分片,性能打折扣;建议设为 4M~32M(根据可用内存调整) - 强制指定算法可用
/*+ HASH_JOIN(t1, t2) */提示,但仅当确定驱动表较小且无索引可用时才用 - 如果发现本该走 hash 却走了 NLJ,先查
SHOW WARNINGS,常因统计信息不准或隐式类型转换导致索引失效,间接阻止 hash 选用
Hash Join 看似是“换了个算法”,实则改变了数据访问模式。最容易被忽略的是:它极度依赖驱动表的大小估算准确性——一旦优化器误判“小表”实际很大,哈希建表失败,就会 fallback 到更慢路径,甚至卡住查询。所以观察 rows 估算值比看算法名更重要。

















