distinct本身不聚合,与count、sum等混用会触发先去重再聚合的双重开销;优化重点是“去重+聚合”组合动作,而非单独调整distinct写法。

直接说结论:DISTINCT 本身不聚合,和 COUNT、SUM 等混用时极易触发双重开销——先去重再聚合,或反过来;真正要优化的是“去重+聚合”这个组合动作,而不是单独调 DISTINCT 的写法。
为什么 DISTINCT + 聚合函数常慢得离谱?
常见错误是写 SELECT DISTINCT user_id, COUNT(*) 这类语句,语法可能通过,但语义矛盾:DISTINCT 要求行唯一,COUNT(*) 又要统计每组行数,数据库往往被迫先全量分组(GROUP BY),再对结果去重,中间生成大量临时数据。
- 执行计划里出现
Using temporary; Using filesort是典型信号 -
COUNT(DISTINCT col)看似简洁,但底层仍是哈希/排序去重,基数越高越慢 - 多个
COUNT(DISTINCT ...)同时出现(如 UV + PV),Hive/MySQL 默认会为每个启动独立 job,IO 和调度开销翻倍
用 GROUP BY 替代 DISTINCT + 聚合的实操条件
当你要的是“每个用户最新订单时间”或“每类商品平均价格”,GROUP BY 不仅语义清晰,且更容易走索引优化路径。
- 确保
GROUP BY字段顺序与联合索引完全一致,例如查询SELECT user_id, MAX(order_time) FROM orders GROUP BY user_id,必须建索引(user_id, order_time) - MySQL 5.7+ 中,若索引覆盖全部 SELECT 字段,
GROUP BY可能触发松散索引扫描(Loose Index Scan),跳过排序 - 避免在
GROUP BY后加HAVING过滤,应把条件前移到WHERE,比如WHERE status = 'paid',否则仍需全量分组后再筛
高基数去重场景下绕开 DISTINCT 的替代方案
当表有千万级记录,但去重后只几百个值(如活跃用户 ID),硬扫全表 + 去重就是浪费资源。
- 先用
EXISTS或IN(带索引)快速捞出候选集,再对小结果集去重:SELECT DISTINCT user_id FROM events WHERE dt = '2026-06-23' AND user_id IS NOT NULL - Hive 场景下,启用
hive.map.aggr=true让 Map 端预聚合,减少 Shuffle 数据量 - 实时性要求不高时,改用近似计算:
approx_distinct(user_id)(Hive 2.1+)误差 1~2%,速度提升 5 倍以上
索引怎么建才让 DISTINCT 快起来?
关键不是“有没有索引”,而是“索引是否覆盖查询路径”。DISTINCT 能跳过排序的前提是:数据库能顺序扫描索引,边读边去重。
- 单字段:如
SELECT DISTINCT status FROM orders→ 建INDEX(status) - 多字段:如
SELECT DISTINCT region, product_type FROM sales→ 必须建INDEX(region, product_type),顺序不能反 - 带 WHERE:如
SELECT DISTINCT a, b FROM t WHERE c = 1→ 建INDEX(c, a, b),c必须是前导列 - 验证方式:用
EXPLAIN查看type是否为index,且Extra中不含Using filesort或Using temporary
最容易被忽略的一点:DISTINCT 性能瓶颈往往不在 SQL 写法本身,而在它暴露了上游数据模型缺陷——比如缺失唯一约束、JOIN 条件漏写、状态字段未归一化。先查 EXPLAIN,再看业务逻辑,最后动索引,顺序错了反而白忙活。











