关联子查询本身不直接走覆盖索引,其效率提升依赖优化器是否将其重写为JOIN;MySQL 8.0+常转为SEMI JOIN并利用被驱动表索引,老版本则易退化为逐行执行的DEPENDENT SUBQUERY,此时索引基本无效。

关联子查询本身不直接走覆盖索引,但它的执行效率是否能被索引提升,取决于它最终被优化器重写成什么形式——多数时候是转成 JOIN,此时覆盖索引才真正起作用。
关联子查询在MySQL里实际怎么执行
MySQL 8.0+ 对大多数关联子查询(如 WHERE x IN (SELECT y FROM t2 WHERE t2.a = t1.b))会尝试自动改写为 SEMI JOIN;老版本则可能走 DEPENDENT SUBQUERY,逐行调用子查询,性能极差。
- 用
EXPLAIN看执行计划:如果出现select_type=DEPENDENT SUBQUERY,说明没重写成功,基本无法利用被驱动表的索引 - 若变成
select_type=SIMPLE且有type=ref或eq_ref,说明已转为 JOIN,这时索引才有意义 - 被驱动表的
ON条件字段(比如t2.a)必须有索引,否则即使改写也退化为全表扫描
覆盖索引对子查询“间接生效”的条件
覆盖索引不作用于子查询语法本身,只在子查询被等价转为 JOIN 后,对被驱动表的扫描阶段起效。
- 子查询中
SELECT的字段必须全部落在联合索引里,例如子查询是SELECT id FROM orders WHERE user_id = t1.id,那就建INDEX idx_user_id_id ON orders(user_id, id) - 如果子查询带
GROUP BY或ORDER BY,这些字段也要纳入索引顺序,否则仍会触发Using filesort或临时表 -
WHERE中的其他过滤条件(如status = 'shipped')要放在联合索引最左侧,否则索引下推(ICP)失效,导致多余行被读入再过滤
为什么有时候加了索引子查询还是慢
常见错因不是索引没建,而是子查询结构让优化器放弃改写。
- 子查询里用了
ORDER BY ... LIMIT 1,MySQL 很可能拒绝转为 JOIN,坚持用DEPENDENT SUBQUERY - 子查询含聚合函数(
COUNT(*)、MAX())且外层没对应分组,优化器通常不重写 - 子查询引用了外层多个列(如
t1.a = t2.x AND t1.b = t2.y),而被驱动表只对x建了单列索引,y没索引,就会放弃使用索引 - 外层表结果集太大(比如没加
WHERE过滤),驱动表行数过多,优化器可能判定 NLJ 成本高于全表扫描,干脆不用索引
覆盖索引对子查询的加速始终是“寄生”在 JOIN 改写之上的。没看到 EXPLAIN 里被驱动表那行出现 Using index,就别默认它生效了。

















