bigywater.tech
MySQL / InnoDB · 37

Hash Join

对比内表无索引场景下,优化器默认选择的Hash Join和强制禁用后退化的裸嵌套循环,看Hash Join把join代价压缩了多少。

0.0009s
Hash Join完成2000×2000行join的耗时
0.3793s
裸嵌套循环的耗时
421倍
两者的耗时差距

做的事:内表没有索引,看优化器默认会怎么选

上一篇验证了内表无索引时优化器不选BKA,这次专门测Hash Join本身的效果。

建表插入CREATE TABLE t19a (id INT PRIMARY KEY AUTO_INCREMENT, k INT); CREATE TABLE t19b (id INT PRIMARY KEY AUTO_INCREMENT, k INT, payload VARCHAR(50)); 各插入2000行,t19b.k上没有任何索引

验证:没有可用索引时,优化器默认选Hash Join

这是MySQL 8.0.18之后才有的能力。

结果SET SESSION optimizer_switch='hash_join=on,block_nested_loop=on'; SELECT COUNT(*) FROM t19a JOIN t19b ON t19a.k=t19b.k Hash Join(默认选择): 耗时 0.0009 s, 结果 2000

0.0009秒就返回了正确结果(2000)——Hash Join的做法是:先把较小的一边(t19a)在内存里建成一张哈希表(key是t19a.k),再扫描一遍t19b,对每一行直接用t19b.k去哈希表里做O(1)查找是否匹配,整个过程只需要各扫描一遍两张表,不需要索引。

验证:禁用Hash Join,退化成的暴力嵌套循环

用optimizer_switch把Hash Join和Block Nested Loop都关掉。

结果SET SESSION optimizer_switch='hash_join=off,block_nested_loop=off'; SELECT COUNT(*) FROM t19a JOIN t19b ON t19a.k=t19b.k 强制Nested Loop(禁用hash join): 耗时 0.3793 s, 结果 2000

同样2000行,同样正确的结果(2000),耗时从0.0009秒暴涨到0.3793秒——差了约421倍。原因是:没有Hash Join也没有Block Nested Loop缓冲优化,退化成最原始的嵌套循环——对t19a的每一行,都要把t19b从头到尾扫一遍去找匹配,总比较次数接近2000×2000=400万次,这是的O(n×m)代价。

Hash Join解决的正是这类"两边都没有可用索引、只能靠等值比较做join"的场景——把O(n×m)的暴力比较,通过先建哈希表的方式压缩成接近O(n+m)。这也是为什么本系列上一篇会发现BKA很少被选中:Hash Join作为更新、覆盖场景更广的策略,在的现代优化器决策里,基本上取代了BKA原本要解决的那部分问题——两边都没索引时用Hash Join,一边有索引时走普通的索引查找join,BKA能覆盖的场景被这两者夹在中间,几乎没有生存空间了。

分步演示:Hash Join比裸嵌套循环快多少

每一步都是本文上面用MySQL跑出来的原始耗时。

Hash Join把O(n×m)压缩成接近O(n+m)
这一步新验证的策略

    本文用到的工具

    • 本机 MySQL 26.7.0(InnoDB),2000×2000行、内表无索引的join
    • optimizer_switch hash_join / block_nested_loop:对比Hash Join和裸嵌套循环
    京ICP备2026067502号-1京公网安备11010502063487号