COUNT(DISTINCT column)变慢是因为需全表扫描、构建哈希表或排序去重,内存不足时落盘,I/O与CPU开销剧增;索引仅在覆盖索引且无回表需求时可能加速,但通常无法避免去重结构构建;近似统计(如HyperLogLog)适用于可接受误差的场景。

为什么 COUNT(DISTINCT column) 会变慢
当表数据量大、column 值分布广或缺乏索引时,COUNT(DISTINCT column) 需要先去重再计数,MySQL/PostgreSQL 等引擎通常走临时表 + 排序(或哈希聚合),内存不足时还会落盘,I/O 和 CPU 开销明显上升。尤其在没有 WHERE 条件或过滤条件弱的情况下,几乎要扫描全表。
哪些情况能用索引加速 DISTINCT
只有当查询能命中覆盖索引(即索引包含 column 且满足查询所需字段)时,才可能避免回表和排序。但注意:COUNT(DISTINCT) 本身不走 B+ 树索引的有序性优势,多数情况下仍需构建去重结构。
- MySQL 8.0+ 对单列
NOT NULL的主键或唯一索引列,有时能优化为直接读索引页计数(但非通用) - PostgreSQL 在有
column上的索引且加了WHERE column IS NOT NULL时,可能利用位图扫描跳过空值,但 DISTINCT 逻辑仍在内存中执行 - 如果只是查「是否存在重复」,用
SELECT 1 FROM table GROUP BY column HAVING COUNT(*) > 1 LIMIT 1可能比 COUNT(DISTINCT) 更早终止
替代方案:近似去重统计要不要考虑
业务能接受误差(比如用户去重数偏差
- PostgreSQL:用
APPROX_COUNT_DISTINCT(column)(基于 HyperLogLog) - ClickHouse:
uniq(column)或uniqCombined(column),速度快且内存可控 - MySQL:无原生支持,但可通过采样(如
TABLESAMPLE)+ 校准系数估算,例如SELECT COUNT(DISTINCT column) * 10 FROM t TABLESAMPLE (10)
注意:采样法对倾斜数据(如少数值占 90%)误差极大,务必先验证分布。
实际写法与常见翻车点
COUNT(DISTINCT) 看似简单,但组合使用时容易出错:
- 不能嵌套聚合:写成
COUNT(DISTINCT SUM(x))会报错,需子查询先算SUM再去重 - 多列去重是按行组合判断:
COUNT(DISTINCT a, b)统计的是不同(a,b)对的数量,不是分别去重后相加 - NULL 值被自动忽略:若列允许 NULL,
COUNT(DISTINCT column)不计入任何 NULL,无需额外WHERE column IS NOT NULL(但加了也不影响结果) - 在
GROUP BY中混用时,别漏掉分组字段:写成SELECT user_id, COUNT(DISTINCT order_id) FROM orders GROUP BY user_id是对的;若漏掉GROUP BY,MySQL 5.7+ 会报错(ONLY_FULL_GROUP_BY 模式开启时)
真正卡住性能的,往往不是语法,而是没意识到 DISTINCT 的底层代价——它不像普通 COUNT(*) 那样能靠索引元数据快速返回。上线前最好在生产数据量级下压测执行计划和耗时。


















