exists 在 where 中做存在性判断时无需 distinct,因其语义天然去重且可短路退出;而 in + distinct 须全量扫描并排序去重,性能更差。

直接说结论:当你要在 WHERE 子句中做“存在性判断”并附带去重逻辑时,EXISTS 不是“可以替代 DISTINCT”,而是根本不需要 DISTINCT —— 因为它的语义和执行机制天然规避了重复扫描和排序开销。
为什么 WHERE 中的子查询不能用 DISTINCT
DISTINCT 是作用于最终结果集的集合操作,而 WHERE 子句里只接受布尔表达式。你在写 WHERE id IN (SELECT DISTINCT id FROM ...) 时,DISTINCT 看似“去重”,实则徒增负担:
- 数据库必须先执行完整子查询,生成临时结果集,再对它排序/哈希去重
- 哪怕主表某
id在子表中出现 100 次,IN仍要等全部扫描完才开始匹配 -
IN遇到子查询返回NULL会整个条件失效(SQL 标准行为),但业务上你往往只关心“有没有”,不care NULL 怎么参与比较
EXISTS 替代 IN + DISTINCT 的写法要点
把 WHERE col IN (SELECT DISTINCT col FROM t WHERE ...) 改成 WHERE EXISTS (SELECT 1 FROM t WHERE t.col = main.col AND ...),关键不是语法替换,而是语义对齐:
- 子查询里写
SELECT 1或SELECT NULL都行,值本身不被使用,只用来触发存在性检查 - 必须把关联条件(如
t.col = main.col)写进子查询的WHERE,否则变成非相关子查询,可能全表扫 - 如果原
IN子查询没关联主表(比如固定列表),那EXISTS就不适用,别硬套
性能差异到底卡在哪几个环节
对比执行计划,慢就慢在这三处:
-
IN + DISTINCT:子查询走INDEX SCAN或更糟的TABLE SCAN→ 78% 资源耗在反复读取;接着SORT去重 → 白占内存还拖慢 -
EXISTS:优化器能利用customer_id索引快速定位 → 找到第一条匹配即短路退出 → 逻辑读从 15,842 降到 2,156 - 注意:如果子表没在关联字段建索引,
EXISTS也会退化成全表扫,和IN一样慢
什么情况下 EXISTS 反而不如 JOIN 或临时表
别迷信 EXISTS 是万能解。真遇到这些情况,得换思路:
- 子查询本身很重(比如含多表 JOIN、聚合、窗口函数),且被外层调用多次 → 优先考虑物化:用
WITH或临时表预计算 - 你需要主表所有记录,包括那些在子表中不存在的(即想保留
NULL匹配语义)→ 改用LEFT JOIN ... ON ... IS NOT NULL,但务必给ON字段加索引 - 子查询结果集很大且稳定(比如“所有有效客户ID”),又频繁被多个查询复用 → 直接建物化视图或缓存表,比每次跑
EXISTS更省
最常被忽略的一点:EXISTS 的优势完全依赖关联字段是否有索引。没索引时,它只是把“扫全表”从外层挪到了子查询里,总量没变——先看执行计划里的 type 是不是 DEPENDENT SUBQUERY,再决定要不要加索引或改写。










