mysql的join底层必走nested loop join(nlj),其性能取决于驱动表行数与被驱动表是否能用索引:type=all即全表扫描,10万×100万=1000亿次i/o;有效索引须建在on右侧字段,且字段类型须一致;inlj靠索引快速定位,bnlj依赖join_buffer批量比对;hash join仅适用于等值连接且无索引场景,有索引时优化器仍优先选inlj。

MySQL的Nested Loop Join到底怎么跑的
MySQL执行JOIN时,只要没触发Hash Join或Merge Join,底层就是Nested Loop Join(NLJ)在干活——它不是一种可选策略,而是所有JOIN的执行基底。所谓“嵌套循环”,就是外层取一行、内层匹配一次,真实执行时根本不存在“并行扫描”或“自动跳过”的魔法。
为什么EXPLAIN里type=ALL就该立刻警觉
当EXPLAIN输出中被驱动表的type列是ALL,说明NLJ正在用最暴力的方式运行:驱动表每1行,被驱动表就要全表扫1次。10万行驱动 × 100万行被驱动 = 1000亿次磁盘I/O,这不是慢,是卡死。
- 真正生效的索引必须落在
ON子句右侧字段上(比如LEFT JOIN orders ON users.id = orders.user_id,索引得建在orders.user_id) - 字段类型不一致会触发隐式转换,哪怕右表有索引也等于没建(
VARCHAR(32)关联VARCHAR(64)就失效) -
STRAIGHT_JOIN能强制左表为驱动表,但若左表过滤后仍有几十万行,再好的索引也救不了右表被扫成筛子
Index Nested-Loop Join(INLJ)和Block Nested-Loop Join(BNLJ)的区别在哪
INLJ和BNLJ都是NLJ的变体,区别只在于“被驱动表有没有索引可用”:
- INLJ:被驱动表关联字段有有效索引 → 每次用B+树
O(log N)定位,快且稳 - BNLJ:没索引 → 把驱动表数据块塞进
join_buffer,再拿整块去扫被驱动表比对 →Extra列出现Using join buffer (Block Nested Loop)就是它在干活 -
join_buffer_size默认仅256KB,大关联时极易溢出到磁盘临时表,性能断崖下跌
MySQL 8.0+的Hash Join没让NLJ消失,反而让它更关键
Hash Join只在等值连接(a.id = b.id)且被驱动表无索引时才可能启用,但它不解决所有问题:
- 非等值条件(
、<code>BETWEEN、LIKE 'abc%')依然只能走NLJ - 内存不足时Hash Join会退化为磁盘哈希,比BNLJ还慢
- 一旦你给被驱动表补上索引,优化器大概率立刻切回INLJ——因为索引查找比哈希构建+探测更轻量
真正容易被忽略的是:NLJ本身无法绕过,但它的实际开销完全取决于“驱动表多小”和“被驱动表能不能用索引跳着找”。别盯着算法名字看,盯EXPLAIN里rows和type这两列,才是真功夫。











