count(distinct)自动忽略null、多列需用行构造器(col1,col2)、大数据量易性能瓶颈;null处理应结合coalesce,多列兼容性因数据库而异,高基数场景推荐近似函数或索引优化。

COUNT(DISTINCT) 能直接统计去重后的行数,但不是所有场景都适用——它在 NULL 值、多列组合、大数据量下行为容易出错,盲目套用反而导致结果偏差。
NULL 值会被自动忽略,且无法通过 WHERE 过滤补救
COUNT(DISTINCT col) 遇到 NULL 时直接跳过,不计入结果,也不报错。这和 GROUP BY 中的 NULL 处理逻辑一致,但常被误认为“漏数据”。
常见错误现象:字段存在大量 NULL,但 COUNT(DISTINCT col) 返回值远小于预期,检查后发现是 NULL 全被剔除了。
实操建议:
- 先用
SELECT COUNT(*), COUNT(col), COUNT(DISTINCT col) FROM table对比三者差异,确认NULL占比 - 若需把
NULL当作一种独立取值统计,改用COUNT(DISTINCT COALESCE(col, 'NULL_PLACEHOLDER')) - 避免在
WHERE col IS NOT NULL后再套COUNT(DISTINCT col)——这不会改变 NULL 被跳过的事实,只是缩小了扫描范围
多列去重必须用括号包裹,否则语法报错
想统计「用户ID + 操作类型」的唯一组合数,写成 COUNT(DISTINCT user_id, action_type) 是错的——MySQL 会报 ERROR 1241 (21000): Operand should contain 1 column(s)。
正确写法是把多列包进一个行构造器(row constructor):
COUNT(DISTINCT (user_id, action_type))
注意括号不能省:(user_id, action_type) 是一个元组表达式,而 user_id, action_type 是参数列表,语义完全不同。
兼容性提醒:
- MySQL 5.7+ 和 8.0 支持该写法
- PostgreSQL 不支持
COUNT(DISTINCT (a,b)),需改用子查询:SELECT COUNT(*) FROM (SELECT DISTINCT user_id, action_type FROM t) AS _ - SQLite 仅支持单列
DISTINCT,多列必须用子查询
大数据量下 COUNT(DISTINCT) 可能触发临时磁盘表或 OOM
当去重字段基数高(如 UUID)、数据量超百万时,COUNT(DISTINCT) 内部会构建哈希表或排序缓冲区。内存不足时,MySQL 会写入 tmpdir 的磁盘临时表,性能断崖下跌;Spark 或 Presto 中甚至可能因 executor memory 不足而失败。
实操建议:
- 加
EXPLAIN看是否出现Using temporary; Using filesort - 对高频统计字段建复合索引(如
INDEX(status, user_id)),可加速COUNT(DISTINCT user_id) WHERE status = 'active' - 近似统计可换
APPROX_COUNT_DISTINCT()(BigQuery / Spark SQL)或HLL_COUNT.INIT()(ClickHouse),误差率通常
真正难的不是写出 COUNT(DISTINCT),而是判断它此刻返回的数字到底代表什么——是业务定义的“唯一用户”,还是数据库按 NULL/类型/排序规则隐式裁剪后的结果。每次执行前,值得花 10 秒跑个 SELECT DISTINCT 抽样看看实际有哪些值。










