DISTINCT与聚合函数不能并列使用,因语义冲突导致双重扫描和冗余开销;应改用GROUP BY替代,并确保索引覆盖、顺序一致、WHERE前移。

DISTINCT 和聚合函数不能直接“并列使用”,因为语义冲突会强制数据库先全量计算再过滤,性能断崖式下跌——这不是写法错,而是执行逻辑必然导致的冗余开销。
为什么 SELECT DISTINCT a, COUNT(*) 会慢得离谱
这类写法看似合理,实则触发双重扫描:数据库必须先把所有行按 a 分组(隐式 GROUP BY),算出每组的 COUNT(*),再对结果集(a, COUNT(*))做行级去重。但分组后 a 本就唯一,DISTINCT 完全冗余,却让优化器放弃索引、启用临时表和排序。
- EXPLAIN 中出现
Using temporary; Using filesort是典型信号 - 如果表有 100 万行,分组后剩 1 万组,
DISTINCT仍要对这 1 万行再哈希/排序 -
SELECT DISTINCT user_id, MAX(order_time)同样危险——MAX()已由分组保证,DISTINCT只是徒增开销
用 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 字段,可能触发松散索引扫描(Loose Index Scan),跳过排序
- WHERE 条件必须前移,避免写成
HAVING status = 'paid'——否则仍需全量分组后再筛 - 别在
GROUP BY后加DISTINCT,如SELECT DISTINCT dept_id, COUNT(*) FROM t GROUP BY dept_id:逻辑上矛盾,dept_id在分组后已唯一
COUNT(DISTINCT x) 的性能陷阱与绕过方案
COUNT(DISTINCT x) 底层仍是哈希或排序去重,当 x 基数高(比如千万级用户 ID),内存哈希表极易膨胀,甚至落盘。
- 多个
COUNT(DISTINCT)同时出现(如 UV + PV),MySQL/Hive 默认为每个启动独立聚合流程,IO 翻倍 - 高频低基数场景(如只几百个活跃城市),优先用
EXISTS或IN缩小候选集:SELECT DISTINCT city FROM locations WHERE country = 'CN',比全表扫快得多 - 实时性要求不高时,用近似函数:
approx_distinct(user_id)(Hive 2.1+),误差 1~2%,速度提升 5 倍以上 - 索引必须建对:多字段
SELECT DISTINCT a, b FROM t→ 建联合索引(a, b),顺序不能反;带 WHERE 时,如WHERE c = 1,可建(c, a, b)覆盖索引
窗口函数里嵌套 DISTINCT 的合法用法
注意:COUNT(DISTINCT col) OVER (PARTITION BY x) 是完全合法且高效的操作,它是在每个窗口内独立去重计数,不违反执行顺序规则。
- 例如统计“每个品类下不同品牌数”:
COUNT(DISTINCT brand) OVER (PARTITION BY category) - 但反过来,
SELECT DISTINCT *, SUM(x) OVER ()就不行——DISTINCT作用于最终结果,而窗口函数需要原始行集 - 真正想“取每个用户最新一条订单 + 附带该用户总订单数”,应拆成 CTE:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn FROM orders), stats AS (SELECT user_id, COUNT(*) AS total_cnt FROM orders GROUP BY user_id) SELECT r.*, s.total_cnt FROM ranked r JOIN stats s ON r.user_id = s.user_id WHERE r.rn = 1
最易被忽略的一点:DISTINCT 是否走索引,不取决于“有没有索引”,而取决于“SELECT 所有字段是否严格匹配某个联合索引的最左前缀”,且 WHERE 条件也必须命中该索引——少一个字段、顺序错一位、条件漏一列,索引就失效。


















