MySQL多表关联底层核心算法是Nested-Loop Join(NLJ),所有变体(INLJ、BNLJ、Hash Join)均为其优化形式:有索引走INLJ,无索引且内存充足走BNLJ,MySQL 8.0+支持Hash Join仅用于等值连接。

MySQL执行器处理多表关联时,底层只有一种核心算法:Nested-Loop Join(NLJ),所有 JOIN 都是它的变体——不是“选哪种”,而是“NLJ 怎么优化”。索引存在与否、版本高低、内存配置,决定它跑的是哪一版 NLJ。
Index Nested-Loop Join(INLJ):有索引时的默认选择
这是生产环境最该追求的路径。只要被驱动表的 ON 字段上有有效索引(类型一致、无隐式转换),优化器几乎必然走 INLJ。
- 驱动表每取出一行,就用其关联值在被驱动表的 B+ 树索引中做一次
O(log N)查找,而非全表扫 - 关键约束:
EXPLAIN中被驱动表的type必须是ref、eq_ref或const;若出现ALL,说明索引没生效 - 常见失效场景:
orders.user_id是VARCHAR(32),但关联字段传入的是VARCHAR(64)→ 触发隐式转换,索引失效 - 示例语句:
SELECT * FROM users JOIN orders ON users.id = orders.user_id,索引必须建在orders.user_id上才有效
Block Nested-Loop Join(BNLJ):无索引但内存够用时的折中
当被驱动表关联字段无索引,且 MySQL 版本
- 核心动作:把驱动表的若干行(受
join_buffer_size限制)批量装入内存Join Buffer,再让被驱动表只被扫描一次,与整个 buffer 比对 -
EXPLAIN的Extra列出现Using join buffer (Block Nested Loop)就是它在运行 - 默认
join_buffer_size = 256KB,大关联下极易溢出 → 触发磁盘临时表,性能断崖下跌 - 注意:一个含 N 张表的 JOIN 会分配 N−1 个 join buffer,不是只用一个
Hash Join:MySQL 8.0+ 对等值关联的大表救星
它不是替代 NLJ,而是 NLJ 在特定条件下的高效实现分支——仅用于等值连接(=)、且被驱动表无可用索引时。
- 流程分两步:
Build阶段扫描小表构建哈希表;Probe阶段扫描大表,用哈希值直接查表,平均O(1)定位 -
EXPLAIN FORMAT=JSON输出里能看到"hash_join": true节点 - 不支持
LIKE、BETWEEN、>等非等值条件;一旦你给被驱动表补上索引,优化器大概率立刻切回 INLJ - 内存不足时会退化为磁盘哈希,比 BNLJ 还慢,所以
hash_join_buffer_size需按需调大
真正容易被忽略的点在于:NLJ 本身无法绕过,但它的实际开销完全取决于「驱动表过滤后有多小」和「被驱动表能不能跳着找」——而不是你写了 LEFT JOIN 就一定左表驱动,也不是表本身行数少就天然适合当驱动表。











