子查询中加 DISTINCT 会因阻止条件下推而导致索引失效,触发全表扫描、临时表和文件排序;IN (SELECT DISTINCT ...) 比 EXISTS 更易踩坑,应优先用 EXISTS 或覆盖索引优化。

子查询里加 DISTINCT 会让优化器放弃走索引
不是 DISTINCT 本身“破坏”索引,而是它改变了优化器的执行路径选择。当子查询包含 DISTINCT,数据库(尤其是 MySQL、SQL Server)往往无法将外部 WHERE 条件下推到子查询内部,导致先完成去重再过滤——而这时索引已失去“快速定位”的作用。
常见错误现象:EXPLAIN 显示 type = ALL 或 type = index,key 字段为空或指向非预期索引,甚至出现 Using temporary; Using filesort。
- 子查询中
DISTINCT通常触发临时表 + 排序,哪怕目标字段有索引,优化器也倾向全扫+哈希去重,而非反复回表查 - 如果子查询还带
JOIN或弱条件(如LIKE '%abc'),DISTINCT会放大全表扫描代价 - MySQL 8.0+ 对单列
DISTINCT有松散索引扫描优化,但要求该列是复合索引最左前缀,且无其他干扰条件(如函数包装、类型隐式转换)
IN (SELECT DISTINCT ...) 比 EXISTS 更容易踩索引失效坑
这是高频踩坑点。IN 子句里的 DISTINCT 不仅不提升性能,反而常让优化器误判结果集大小,放弃使用 customer_id 上的索引,转而对子查询表做全扫描。
使用场景:查“有订单的客户信息”,底层 orders 表有千万级数据,customer_id 列上有索引。
- 低效写法:
SELECT * FROM customers WHERE customer_id IN (SELECT DISTINCT customer_id FROM orders WHERE order_date > '2024-01-01')→ 可能全扫orders - 高效替代:
SELECT * FROM customers c WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id AND o.order_date > '2024-01-01')→ 可走customer_id索引 + range 扫描 - 若必须用
IN,确保子查询能走索引:给orders(order_date, customer_id)建覆盖索引,让DISTINCT只扫索引页,不回表
视图定义含 DISTINCT 会导致所有调用都继承索引失效风险
视图不是“快照”,而是保存的 SQL 语句模板。一旦定义里写了 DISTINCT,所有基于该视图的查询都会强制先去重——哪怕你后续加了强 WHERE 条件,优化器也大概率先执行去重逻辑,再过滤。
容易被忽略的是:这种失效是静默的。没有报错,EXPLAIN 却显示 key = NULL,I/O 和内存消耗悄悄翻倍。
- 检查方式:对视图执行
EXPLAIN FORMAT=TREE SELECT * FROM my_view WHERE x = ?,看是否出现materialize或temporary table节点 - 修复思路:删掉视图里的
DISTINCT,把去重逻辑下推到调用方(如外层GROUP BY或应用层 dedup) - 实在要保留去重,改用物化方案:定时把
DISTINCT结果写入带索引的汇总表,视图查这张表
GROUP BY 替代 DISTINCT 并不一定更优,关键看索引匹配度
GROUP BY 和 DISTINCT 在语义和执行路径上高度相似,但优化器对两者的索引利用策略略有差异。不能默认“换 GROUP BY 就行”。
参数差异:当去重字段与查询返回字段完全一致时,两者计划可能相同;但只要多出一列(比如 SELECT DISTINCT a, b FROM t vs SELECT a, b FROM t GROUP BY a, b),优化器就可能选择不同路径。
- 有覆盖索引时(如
INDEX(a, b)),DISTINCT a, b可能直接走索引扫描,GROUP BY a, b却可能触发排序 - 没索引时,
DISTINCT常比GROUP BY少一次分组排序,但差别微乎其微,不如优先建索引 - 真正有效的替代是去掉冗余 JOIN 导致的假重复,而不是在
DISTINCT和GROUP BY之间切换
真正容易被忽略的是:DISTINCT 在子查询里像一层“语义雾”,它不报错、不警告,却让索引在后台彻底失能。上线前必须用 EXPLAIN FORMAT=TREE 看清它到底扫了什么表、用了哪个索引、有没有建临时表。

















