MySQL 中 IN
子查询到底是怎么执行的
MySQL 对
IN子查询的处理方式取决于子查询是否相关(correlated)、结果集大小、是否有索引,以及 MySQL 版本(5.6/5.7/8.0 差异明显)。5.7+ 默认会尝试将非相关
IN转为半连接(
semi-join),用
FirstMatch或
LooseScan策略优化,避免对主表每行都重复执行子查询。
但一旦子查询里用了
ORDER BY、
LIMIT、或引用了外部表字段(即变成相关子查询),优化器就会退回到「对主表每一行,执行一次子查询」的老路——性能断崖式下跌。 如果子查询返回空结果,
IN整体为
FALSE(注意:
NULL会导致整个表达式为
UNKNOWN)
IN后面的列表或子查询结果中只要有一个
NULL,且主查询值不匹配任何非
NULL项,则结果是
UNKNOWN,不是
FALSE,这在
WHERE条件中会被当作不满足而过滤掉 子查询若无索引,又返回大量行,
IN可能触发临时表 + 文件排序,
EXPLAIN中看到
Using temporary; Using filesort
EXISTS
的执行逻辑和关键特性
EXISTS是纯粹的「存在性检查」,不关心子查询返回什么列、多少行,只看是否至少有一行。优化器几乎总是将其转为半连接(即使没开
semi_join开关),且天然支持短路:找到第一行就退出,不继续扫描。
它天然适合相关子查询场景,因为语法结构强制关联(比如
WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.id = t1.ref_id)),而优化器能利用
t2.ref_id上的索引快速定位。
EXISTS对
NULL不敏感——子查询里哪怕只有
SELECT NULL,只要有行返回,结果就是
TRUE写成
NOT EXISTS时,无法利用大多数半连接优化,可能回退到嵌套循环,需特别留意执行计划 子查询中加
SELECT 1或
SELECT *对性能无实质影响,MySQL 会忽略 SELECT 列表内容
ANY
(含 = ANY
)和 IN
的等价性与陷阱
col = ANY (subquery)在语义和执行上基本等价于
col IN (subquery),优化器通常做相同处理。但二者在
NULL处理上完全一致:只要子查询结果含
NULL,且没有匹配项,整个表达式就是
UNKNOWN。
真正容易出问题的是
> ANY、
这类形式——它们不等价于 <code>IN,而是等价于「比子查询结果中最小/最大值满足条件」。例如:
salary > ANY (SELECT salary FROM manager)意思是「工资高于任一经理的工资」,即「高于最低经理工资」。
> ANY等价于
> MIN(...);
等价于 <code>
= ANY和
IN在单列、非 NULL 场景下可互换,但
IN更通用、可读性更好,建议优先用
IN如果子查询返回空集,
= ANY返回
FALSE;但
> ANY返回
FALSE(因无最小值可比较),这点常被忽略
怎么一眼看出执行效率高不高
别猜,直接看
EXPLAIN FORMAT=TREE(MySQL 8.0)或
EXPLAIN的
type和
Extra字段。核心盯三点: 是否出现
materialized(物化子查询)——说明子查询被缓存为临时表,后续用哈希查找,通常是好事
Extra里有没有
Using join buffer (Block Nested Loop)或
Using where; Using index——前者危险,后者健康 子查询部分的
rows值是否远小于主表
rows,且有
key显示走了索引
EXPLAIN SELECT * FROM user u WHERE u.id IN (SELECT user_id FROM order WHERE status = 'paid');
如果
order(user_id, status)有联合索引,这条大概率走
range+ 物化;如果没有索引,
order表可能全表扫描,
rows显示几十万,那就得立刻加索引。
最常被忽略的一点:相关子查询里,外层字段没走索引,或者子查询里写了
SELECT *却没用到所有列——这不会报错,但会让优化器放弃某些优化路径,老老实实嵌套循环。
