IN适合小结果集且值固定,EXISTS需相关子查询;NOT IN遇NULL全过滤,EXISTS无视NULL;性能取决于表大小与驱动方向。

IN 适合查静态列表或小结果集
当你明确知道要匹配的值是几个固定数字、字符串,或者子查询返回的数据量很小(比如几十到几百行),IN 最直接也最易读。它本质是把右边的值全部取出来,放进一个临时集合,再逐行比对左边字段是否“在里面”。
常见错误现象:NOT IN 遇到子查询结果含 NULL 时,整条语句可能不返回任何数据——因为 value NOT IN (1, 2, NULL) 在 SQL 三值逻辑中判定为 UNKNOWN,被当作 FALSE 过滤掉。这是线上事故高发点。
实操建议:
- 用
IN (1, 2, 3)替代写一堆OR条件,更简洁 - 子查询用
SELECT DISTINCT id FROM small_table,避免重复值放大内存开销 - 如果右边是
SELECT id FROM large_table WHERE status = 'active',且该表有百万行,慎用IN
EXISTS 必须配合相关子查询使用
EXISTS 不是函数,不能单独调用;它必须嵌套一个「相关子查询」,即子查询里要引用主查询的列(如 WHERE a.id = b.a_id)。它的语义是:“对主表每一行,执行一次子查询,只要子查询返回至少一行,就保留这行”。
容易踩的坑:
- 写成
EXISTS (SELECT * FROM b)(没关联条件)→ 变成恒真,全表扫 - 子查询里漏加索引字段,比如
b.a_id没索引,每次都要全表扫描b表 - 误以为
SELECT *在子查询里会影响性能——其实数据库只看“是否有行”,字段列表完全无关,用SELECT 1或SELECT NULL更清晰
典型写法:WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = users.id)
性能差异取决于表大小和驱动方向
数据库优化器会尝试重写 IN 和 EXISTS,但实际执行计划仍受数据分布影响。关键不是“哪个绝对快”,而是“谁当驱动表、谁当被查表”更合理。
可观察的规律:
- 如果子查询表(B)远小于主表(A),
IN常更快:B 全量加载进内存后,A 表走索引单次查找即可 - 如果子查询表(B)远大于主表(A),
EXISTS更稳:A 表每行只触发一次 B 表的索引查找,不缓存 B 的全部结果 - 若 A、B 表大小接近,两者性能差异通常不明显,优先选语义更贴切的那个
验证方法:在真实数据上跑 EXPLAIN,重点看 type 是否为 ref 或 range,以及 rows 估算是否合理。
NULL 处理逻辑完全不同
这是最容易忽略、却最致命的区别。它不只影响结果对错,还可能导致线上数据丢失。
IN 对 NULL 敏感:
-
1 IN (1, 2, NULL)→TRUE(能匹配到 1) -
3 IN (1, 2, NULL)→UNKNOWN→ 当作FALSE过滤 -
3 NOT IN (1, 2, NULL)→ 整体为UNKNOWN→ 全部过滤,无结果返回
EXISTS 完全不care NULL:
-
EXISTS (SELECT 1 FROM b WHERE b.id = 3)只看子查询有没有行,b.id 是不是 NULL 不参与判断 - 哪怕 b 表里所有
id都是NULL,只要有一行满足WHERE条件(比如id IS NULL),就返回TRUE
所以涉及 NOT IN 且子查询可能含 NULL 时,必须显式排除:NOT IN (SELECT id FROM b WHERE id IS NOT NULL),或直接改用 NOT EXISTS。










