mysql多表联查的nlj按驱动表→被驱动表链式嵌套执行,驱动表由优化器基于预估行数决定(explain首行为驱动表),被驱动表是否走索引取决于on字段是否有有效索引(含最左前缀与类型匹配),三表及以上仍为逐层嵌套而非全排列。

MySQL执行多表联查时,Nested-Loop Join(NLJ)不是“固定用某张表嵌套另一张”,而是按驱动表→被驱动表逐层展开,每层只做一次外层行到内层匹配的映射,且是否走索引完全取决于被驱动表的关联字段是否有可用索引。
驱动表怎么定:看EXPLAIN第一行,不是看SQL书写顺序
INNER JOIN里哪张表是驱动表,由优化器根据WHERE过滤后的预估行数决定,不是左写谁就是谁。LEFT JOIN左表强制为驱动表,RIGHT JOIN右表强制为驱动表——但即便如此,优化器仍可能调整被驱动表的访问方式。
-
EXPLAIN输出中,id相同、select_type为SIMPLE的行,从上到下就是执行顺序:第一行是驱动表,第二行是第一个被驱动表,第三行是第二个被驱动表(如果是三表JOIN) - 如果
type字段是const或eq_ref,说明驱动表已精准定位(如主键等值查询),这是NLJ最理想的起点 - 若驱动表本身
rows预估很大(比如10万行),哪怕被驱动表有索引,整体NLJ代价也高——因为要跑10万次索引查找
被驱动表走不走索引:只看on字段有没有有效索引
NLJ本身不强制要求索引,但MySQL实际执行时,只要被驱动表的ON条件字段有可用索引(主键、唯一索引、普通索引均可),就会自动降级为Index Nested-Loop Join,避免全表扫描。
- 例如
ON u.id = o.user_id,若orders.user_id无索引,type会是ALL,触发BNL或性能雪崩;若有索引,type通常为ref或eq_ref - 复合索引必须满足最左前缀:
INDEX(user_id, status)能用于ON user_id = ?,但ON status = ?不能触发该索引 - 注意隐式类型转换:比如
user_id是INT,但ON条件里写了CAST('123' AS CHAR),索引直接失效
三表及以上JOIN:NLJ是链式嵌套,不是一次性全连
MySQL不会把三张表一起哈希或一次全排列,而是严格按驱动表→第一被驱动表→第二被驱动表的链式结构执行。中间结果不物化,也不缓存(除非用JOIN BUFFER)。
- 假设
t1 JOIN t2 ON ... JOIN t3 ON ...,执行逻辑近似:for each row r1 in t1: for each row r2 in t2 where r2 matches r1: for each row r3 in t3 where r3 matches r2: output (r1,r2,r3) - 这意味着t3的扫描次数 = t1与t2匹配后的总行数 × 每次匹配的t3扫描开销。如果t1×t2结果集有5000行,而t3没索引,就等于扫t3 5000遍
-
STRAIGHT_JOIN可强制连接顺序,但仅当优化器选错驱动表且你确认更优时才用,否则容易锁死低效路径
真正容易被忽略的是:NLJ的“循环”单位不是SQL里的“表”,而是优化器拆解后的“访问方法”。一次ref索引查找算作内层一次“循环体执行”,而一次ALL扫描才是真正的O(N)暴力循环。看执行计划时,盯紧每一行的type和rows,比背算法名字管用得多。











