mysql如何选择合适的JOIN算法(Nested Loop、Hash Join等)

来源:这里教程网 时间:2026-02-28 20:38:43 作者:

MySQL 8.0.18+ 默认用哪种 JOIN 算法?

MySQL 8.0.18 起,

Hash Join
成为非索引连接场景的默认算法,但前提是:被驱动表(右表)没有可用索引、且连接字段类型兼容、连接条件是等值(
=
),并且优化器估算其比
Block Nested-Loop Join
(BNL)更优。

注意:

Nested-Loop Join
(NLJ)仍是索引存在时的首选——只要驱动表的每行能通过索引快速定位被驱动表匹配行,优化器几乎总选 NLJ,因为它 IO 少、延迟低。

Hash Join
只在
EXPLAIN
Extra
列中显示为
Using join buffer (hash join)
若看到
Using where; Using join buffer (Block Nested Loop)
,说明走的是 BNL(一种带缓冲的 NLJ 变种)
Using join buffer
(无括号说明)通常表示 BNL;而
Using index condition
Using index
出现时,基本可判定是 NLJ

为什么有时候强制用 Hash Join 反而变慢?

Hash Join
需把被驱动表(或其连接字段 + 主键)整个加载进内存构建哈希表。一旦该表太大(比如 > 数百 MB)、或内存不足(
join_buffer_size
不够),就会退化成多次磁盘分片处理,性能断崖式下跌。

典型踩坑场景:

被驱动表含
TEXT
/
BLOB
字段,即使只 select 主键,也可能因 MySQL 内部物化逻辑导致哈希表膨胀
join_buffer_size
设置过小(默认仅 256KB),而实际需要数 MB —— 此时应调大,但注意它是 per-connection 的,别盲目设到 1GB+
连接字段有隐式类型转换(如
INT
vs
VARCHAR
),哈希计算前需统一类型,开销增大且可能失准
非等值连接(
, <code>BETWEEN
)完全不支持
Hash Join
,优化器会直接忽略

如何干预 JOIN 算法选择?

MySQL 不提供

USE HASH JOIN
这类 HINT,但可通过以下方式间接引导:

删掉被驱动表上“干扰性”索引:有时一个低效的索引会让优化器误判 NLJ 成本更低,删除后反而触发
Hash Join
STRAIGHT_JOIN
固定驱动表顺序,再配合
FORCE INDEX
IGNORE INDEX
控制是否走索引,从而影响算法路径
调大
join_buffer_size
(会话级即可):例如
SET SESSION join_buffer_size = 4194304;
(4MB),对中等规模被驱动表提升明显
检查
EXPLAIN FORMAT=TREE
输出,它会明确写出
-> Hash join
-> Nested loop join
,比传统
EXPLAIN
更直观

BNL 和 Simple Nested Loop 的区别在哪?

老版本 MySQL 常见的

Block Nested-Loop Join
(BNL)本质是把驱动表数据分块读入
join_buffer
,再批量去被驱动表做匹配,减少被驱动表扫描次数;而 Simple Nested Loop(未启用 BNL 时)是驱动表每行都单独扫一遍被驱动表,IO 放大严重。

关键控制开关是

optimizer_switch
中的
block_nested_loop
标志(默认 ON)。禁用它:
SET optimizer_switch='block_nested_loop=off';
会强制退回到 Simple Nested Loop —— 仅用于调试对比,生产环境绝不建议。

真正要关注的是:当

join_buffer_size
太小,BNL 的“块”太小,反而导致更多次被驱动表扫描;此时不如让驱动表走索引,切回 NLJ。

哈希表构建和 NLJ 的索引查找,本质上是内存换 IO 和 IO 换 CPU 的权衡,没有银弹——得看你的数据分布、内存余量、以及最痛的是延迟还是吞吐。

相关推荐