SQL 中 IN 和 EXISTS 有什么区别?
简化版
IN 判断左侧值是否属于一个值集合,EXISTS 只判断相关子查询是否至少返回一行。现代数据库可能把两者优化成相近的半连接计划,因此不能断言谁一定更快;选择时先看语义,并特别注意 NOT IN 遇到 NULL 时可能返回 UNKNOWN。
详细版
下面两种写法都可查询有订单的客户:
SELECT * FROM customer
WHERE id IN (SELECT customer_id FROM orders);
SELECT * FROM customer AS c
WHERE EXISTS (
SELECT 1 FROM orders AS o WHERE o.customer_id = c.id
);
EXISTS 子查询的输出列不参与结果,只关心是否存在匹配行,所以习惯写 SELECT 1;写 SELECT * 通常也不改变 EXISTS 的真假。IN 更像集合成员判断,适合短常量列表和直接表达值集合。
完整版教学
一、IN 表达“属于集合”
WHERE status IN ('paid', 'shipped')
右侧可以是常量列表,也可以是返回一列的子查询。子查询返回多列时不能与单个标量直接比较。重复值不会改变 IN 的真假,因为成员关系只关心有没有相等值。
二、EXISTS 表达“是否存在匹配行”
SELECT c.id, c.name
FROM customer AS c
WHERE EXISTS (
SELECT 1
FROM orders AS o
WHERE o.customer_id = c.id
AND o.status = 'paid'
);
子查询引用了外层的 c.id,属于相关子查询。逻辑上对当前客户判断是否至少存在一笔已支付订单。数据库不需要把子查询中的具体列返回给外层。
三、为什么不能背“外表小用 IN,子表小用 EXISTS”
这是把旧版本、特定执行计划经验当成固定规律。MySQL 等现代优化器可以把 IN、EXISTS 转换为半连接,也可能选择物化、索引查找等策略;最终计划由统计信息、索引、数据分布和版本共同决定。
面试中更准确的回答是:两者语义不同但很多等价场景可被优化为相似计划,性能要用 EXPLAIN 和真实数据验证。关联列上是否有合适索引往往比关键字选择更重要。
四、NOT IN 与 NULL 是最大陷阱
SELECT * FROM customer
WHERE id NOT IN (SELECT customer_id FROM blacklist);
如果子查询结果含 NULL,对普通 id 而言,“不等于 NULL”得到 UNKNOWN,整个 NOT IN 条件可能没有任何行满足。更稳妥的反匹配写法是:
SELECT * FROM customer AS c
WHERE NOT EXISTS (
SELECT 1 FROM blacklist AS b WHERE b.customer_id = c.id
);
也可以在 NOT IN 子查询中明确排除 NULL,但仍要确认左侧值自身的空值语义。
五、如何选择
- 固定少量取值:
IN (...)简洁直观; - 强调是否存在关联记录:
EXISTS更贴合语义; - 反关联且空值不可控:优先考虑
NOT EXISTS; - 关心性能:检查索引、统计信息和执行计划,不凭口诀改写。
六、常见误区与追问
| 写法 | 语义关注点 | 高风险点 |
|---|---|---|
IN | 左值是否属于右侧集合 | 右侧集合列数、类型、重复值 |
EXISTS | 相关条件是否至少匹配一行 | 关联条件和索引是否合适 |
NOT IN | 左值是否不属于集合 | 右侧出现 NULL |
NOT EXISTS | 不存在匹配行 | 反关联语义更稳定 |
- 误区:IN 和 EXISTS 谁快有固定答案。 现代优化器可能把二者改写成半连接、物化子查询或索引查找,真实性能要看执行计划和数据分布。
- 误区:EXISTS 子查询里 SELECT 什么会影响结果。
EXISTS只关心是否返回至少一行,SELECT 1是表达习惯,不是性能魔法。 - 误区:NOT IN 和 NOT EXISTS 完全等价。
NOT IN遇到右侧集合含NULL时会受三值逻辑影响,可能导致没有任何行满足条件。 - 追问:什么时候更适合写 IN? 固定少量枚举值、语义上就是集合成员判断时,
IN (...)简洁清楚。 - 追问:什么时候更适合写 EXISTS? 强调外层行是否存在关联记录,尤其是相关子查询、反关联和可空列风险场景,
EXISTS/NOT EXISTS更直观。 - 追问:优化这类 SQL 先看什么? 先看关联列索引、统计信息、子查询是否可去重或物化,再用
EXPLAIN验证计划,而不是只替换关键字。
易错点:
NOT IN的右侧集合只要混入NULL,三值逻辑就可能让反匹配结果和直觉完全不同。
七、加强记忆
IN 问“值在不在集合里”,EXISTS 问“匹配行存不存在”。等价查询可能被优化器变成同类半连接,谁快没有固定答案;真正必须记住的是 SQL 的三值逻辑——NOT IN 的集合只要混入 NULL,就可能让结果全部变成 UNKNOWN。