子查询是否走索引取决于mysql优化器的处理策略;in/exists在特定条件下可走索引,而聚合、函数运算、含limit的子查询等常导致索引失效,建议优先用join或物化提示优化。

子查询是否走索引,取决于它的执行方式
MySQL 对子查询的处理策略直接影响索引能否生效。不是所有子查询都能用上索引,关键看优化器是否将它转为 JOIN 或使用了物化(materialization)/半连接(semi-join)等优化手段。
-
IN子查询在某些条件下会被转成SEMI JOIN,此时外层和内层表都可能走索引 -
EXISTS通常能利用内表的索引(尤其是关联字段有索引时),因为它是“对外层每行做一次快速查找” - 相反,
SELECT * FROM t1 WHERE col IN (SELECT col FROM t2)如果t2.col没索引,内层扫描全表,性能雪崩 - 使用
NOT IN时,只要子查询结果含NULL,整个条件恒为FALSE,且无法走索引 —— 这是常见陷阱
可以用 EXPLAIN 看 type 和 key 字段确认是否命中索引;若显示 ALL 或 index 且没写 key,基本没走有效索引。
哪些子查询结构天然难优化
有些写法会让优化器放弃索引选择,哪怕字段本身有索引。
-
SELECT * FROM t1 WHERE id = (SELECT MAX(id) FROM t2 WHERE t2.status = 1):聚合子查询 + 外部无关联,常导致全表扫描t2 -
SELECT * FROM t1 WHERE name LIKE CONCAT('%', (SELECT keyword FROM config), '%'):子查询结果参与函数运算,索引失效 -
SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 ORDER BY create_time DESC LIMIT 10):含LIMIT的子查询在旧版本 MySQL(
这类场景更稳妥的做法是拆成两步:先查出子查询结果(存在变量或临时表中),再用于主查询 —— 尤其当子查询结果集小且稳定时。
加索引前,先看子查询是否真的必要
很多性能问题根源不在索引缺失,而在过度依赖子查询表达逻辑。
- 关联表不超过 3 张时,
JOIN几乎总比等价子查询快,因为优化器对JOIN的路径选择更成熟 -
WHERE col IN (1,2,3)比WHERE col IN (SELECT id FROM tmp_ids)快得多,尤其当tmp_ids是小结果集 - 如果子查询只是用来过滤“是否存在”,优先用
EXISTS而非IN,前者更容易命中索引且不惧NULL
用 SHOW STATUS LIKE 'Handler_%' 对比两种写法的 Handler_read_next 和 Handler_read_rnd_next 值,能直观看出随机读开销差异。
MySQL 8.0+ 的物化提示(hint)能强制走索引
新版支持通过 /<em>+ MATERIALIZE </em>/ 提示让子查询提前物化为临时表,并自动建哈希索引(内存中)或 B+ 树索引(磁盘上)。
-
SELECT <em> FROM t1 WHERE id IN (/</em>+ MATERIALIZE */ SELECT id FROM t2 WHERE status = 1) - 物化后,
t2的筛选条件status = 1若有索引,就能先快速定位子集,再构建临时索引加速外层匹配 - 但注意:物化本身有内存/磁盘开销,若子查询结果超 10 万行,可能反而变慢;建议配合
optimizer_switch='materialization=on'动态控制
真正影响索引效果的,往往不是“有没有建索引”,而是子查询是否被优化器识别为可索引驱动的结构 —— 这点在复杂嵌套里特别容易被忽略。










