not in 遇 null 返回 unknown 导致结果为空,而 not exists 不受 null 影响且可走索引;大数据量下 not exists 性能更优,left join 或 except 是更可靠替代方案。

NOT IN 遇到 NULL 就失效,不是慢,是错
当子查询返回结果里包含 NULL(比如 SELECT customer_id FROM orders 中某条记录的 customer_id IS NULL),整个 NOT IN 条件会变成 SQL 的三值逻辑中的 UNKNOWN,而不是 TRUE 或 FALSE。这意味着:本该被选出来的行,全被过滤掉了——查不到任何结果,但你完全看不出哪里错了。
而 NOT EXISTS 只关心“有没有匹配行”,NULL 不参与比较,自然不受影响。这是可靠性层面的根本差异,不是性能优化能掩盖的。
NOT IN 强制全表扫描,NOT EXISTS 能用索引
NOT IN (SELECT ...) 会先执行子查询、把结果集全部拉出来(哪怕只用判断“在不在里面”),再对外表逐行比对。这个过程无法利用外表或子查询表上的索引,内外表都容易触发全表扫描。
NOT EXISTS 是关联子查询,数据库优化器能把它转成类似 LEFT JOIN ... WHERE ... IS NULL 的执行计划,只要关联字段(如 orders.customer_id = customers.id)上有索引,就能走索引查找,避免扫全表。
- 子查询表大、外表小 →
NOT EXISTS更快,因为内表可索引驱动 - 子查询表小、外表大 → 即使
IN可能略快,NOT IN仍因 NULL 和扫描风险被主流规范禁用
大数据量下执行计划差异明显
在百万级订单查“无客户归属的订单”时:
— WHERE order_id NOT IN (SELECT id FROM customers) 很可能生成 Hash Match (Aggregate) + 全表扫描,内存占用高、易超时;
— WHERE NOT EXISTS (SELECT 1 FROM customers WHERE customers.id = orders.customer_id) 多数情况下走 Nested Loops 或 Index Seek,响应稳定。
PostgreSQL 和 SQL Server 的 EXPLAIN ANALYZE 都能清晰看到:前者常带 Materialize 步骤,后者直接命中索引节点。
替代方案其实更值得优先考虑
真正处理大数据排除逻辑时,NOT EXISTS 虽比 NOT IN 好,但还不是最优解:
- 用
LEFT JOIN ... WHERE right_table.id IS NULL,语义直白、执行计划更可控 - 用
EXCEPT(如SELECT id FROM orders EXCEPT SELECT id FROM customers),集合运算天然去重、支持并行,且多数现代引擎对其做了深度优化 -
MERGE语句在需要同时做删/改时,比嵌套子查询更简洁安全
真正容易被忽略的是:NOT IN 的危险性不只在性能,它会在含 NULL 时静默返回空结果——这种 bug 往往压测不暴露,上线后才漏数据。











