COUNT(DISTINCT)在大数据下天然慢,因其必须构建哈希表或排序结构去重,内存随唯一值线性增长,超阈值则落盘;无索引时WHERE无法加速,多指标会触发多次独立去重或Expand膨胀,高基数、NULL处理及跨库兼容性亦加剧性能问题。

COUNT(DISTINCT) 本身不是优化手段,而是标准去重计数方式;它在大数据下天然慢,所谓“优化”其实是避免误用、控制数据规模、或主动降级为近似方案。
为什么直接写 COUNT(DISTINCT) 在大表上会变慢
它必须构建去重集合(哈希表或排序),内存占用随唯一值数量线性增长。一旦超出内存阈值,数据库就会落盘建临时表——MySQL 中表现为 Using temporary; Using filesort,PostgreSQL 中触发 HashAggregate + 全表扫描。
- 字段无索引时,连 WHERE 过滤都可能无法加速去重过程
- 如果去重列基数极低(比如只有 3 个状态值),
COUNT(DISTINCT)反而比GROUP BY+COUNT(*)更重 - MySQL 8.0+ 对前缀索引敏感:若
name VARCHAR(255)只建了INDEX(name(10)),去重只基于前 10 字符,结果不可信
先过滤、再聚合:必须把 WHERE 写在最外层
错误写法:SELECT COUNT(DISTINCT user_id) FROM events 扫全表;正确做法是让条件尽早生效。
- 时间范围必须下推:加
WHERE dt >= '2026-09-10',别指望分区裁剪自动帮你省事 - 业务状态要前置:比如只统计
status = 'completed'的订单,这个条件不能放在 HAVING 或子查询里 - 分区表务必显式指定分区键:
WHERE ds = '20260916'比WHERE event_time >= ...更可靠
多个去重指标同时查?别堆一起写
写 COUNT(DISTINCT a), COUNT(DISTINCT b), COUNT(DISTINCT c) 会让引擎跑三次独立去重流程,I/O 和 CPU 都翻倍。
- Spark SQL 会自动改写成
Expand+ 多路Aggregate,但 shuffle 数据量剧增 - 更稳的做法是拆成多个单指标查询,或用子查询预聚合(尤其当 a/b/c 来自不同维度)
- 如果误差可接受,优先换
APPROX_COUNT_DISTINCT(a)(BigQuery/Trino/PostgreSQL 14+ 支持)、或APPROX_COUNT_DISTINCT(b)—— 它们共享同一套 HyperLogLog 结构,开销不叠加
多列组合去重的跨库写法
MySQL 支持 COUNT(DISTINCT a, b),但 PostgreSQL、Oracle、SQL Server 不认——强行写会报错 Incorrect syntax near ','。
- 通用解法:用子查询
SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t) AS _ - 拼接风险高:如
COUNT(DISTINCT CONCAT(a, '|', b)),当 a 或 b 含|就会误合并 - 真正安全的替代是 CTE +
ROW_NUMBER() OVER (PARTITION BY a, b ORDER BY 1)去重标记,再统计,但代价更高
最易被忽略的一点:COUNT(DISTINCT) 的结果永远不包含 NULL,这看似合理,但如果你的业务把空字符串或占位符(如 'unknown')和 NULL 视为不同语义,就得提前用 COALESCE(col, 'null_value') 统一处理——否则“去重数突然变少”可能根本查不到原因。


















