优先用GROUP BY替代DISTINCT以提升去重性能,因GROUP BY更易利用索引、避免全表排序;当去重依赖存在性判断时,则用EXISTS替代IN子查询中的DISTINCT。

直接结论:在 Oracle 或 MySQL 存储过程中,当 DISTINCT 导致慢查询(尤其多列、大结果集),优先尝试用 GROUP BY 替代;若去重逻辑依赖另一张表的存在性判断,则 EXISTS 往往比 IN (SELECT DISTINCT ...) 更高效。
为什么 DISTINCT 在存储过程中容易变慢
存储过程里嵌套的 DISTINCT 查询常被忽视执行代价:它默认触发全结果集排序 + 相邻行比较,且无法利用索引跳过重复扫描。尤其当 SELECT 列表包含 20+ 字段(如你知识库中那个 17544 行耗时 970 秒的语句),数据库必须构造完整行哈希或排序键,内存/临时表压力陡增。Oracle 的执行计划里常见 SORT UNIQUE 操作符,MySQL 则可能回退到 Using temporary; Using filesort。
常见错误现象:
- 存储过程执行时间随数据量非线性增长(10 万行变 2 秒,100 万行变 200 秒)
- AWR/慢日志显示大量
temp space usage或Created_tmp_tables - 加了
INDEX却没改善 —— 因为DISTINCT需要覆盖所有 SELECT 列,单列索引无效
用 GROUP BY 替代 DISTINCT 的实操要点
GROUP BY 和 DISTINCT 在语义上不等价,但纯去重场景下可安全替换,关键在写法和约束。
- MySQL 严格模式(
ONLY_FULL_GROUP_BY)下,SELECT中所有非聚合列必须出现在GROUP BY子句中 —— 这反而是好事,能暴露隐式依赖 - Oracle 不强制此规则,但建议显式写出全部分组列,避免 NLS 排序或隐式类型转换导致意外去重失败
- 性能提升主因:优化器更倾向对
GROUP BY列使用索引范围扫描(尤其当分组列有复合索引时),而DISTINCT往往走全表扫描后排序 - 示例改造:
SELECT DISTINCT a, b, c FROM t WHERE x = ?→SELECT a, b, c FROM t WHERE x = ? GROUP BY a, b, c
什么时候该换 EXISTS 而不是 GROUP BY
当去重逻辑本质是“取 A 表中在 B 表存在的记录”,例如:SELECT DISTINCT id FROM orders WHERE id IN (SELECT DISTINCT order_id FROM order_items) —— 这种写法会先膨胀再收缩,极低效。
- 用
EXISTS改写后,数据库可对每个orders.id做半连接(semi-join),找到第一个匹配即停,避免生成中间重复集 - 必须确保子查询中的关联条件明确,比如
EXISTS (SELECT 1 FROM order_items i WHERE i.order_id = o.id),不能漏掉WHERE关联 - 如果子查询本身还带
DISTINCT,先把它干掉 ——EXISTS不关心数量,只关心存在性 - Oracle 中注意:
NOT EXISTS比NOT IN安全,后者遇到子查询含NULL会整个返回空结果
容易被忽略的细节
真正卡住性能的往往不是关键字本身,而是上下文:
-
GROUP BY后如果加了ORDER BY,且排序字段不在GROUP BY列中,会额外触发一次排序 —— 检查执行计划是否出现两个SORT - Oracle 存储过程中用
SELECT ... INTO接收GROUP BY结果时,必须保证结果恰好一行,否则报ORA-01422;而DISTINCT多行是合法的 - MySQL 8.0+ 支持
GROUP BY的ANY_VALUE()函数绕过严格模式,但别滥用 —— 它返回的是任意一行值,业务语义可能错乱










