APPROX_COUNT_DISTINCT在SQL Server 2019+中仅对千万行以上、高基数列有效,基于HyperLogLog++算法,内存恒约12KB、误差≤2%,但低基数、小表、NULL过多或索引不匹配时反而更慢更不准。

APPROX_COUNT_DISTINCT 在 SQL Server 2019+ 中确实能提速,但只对千万行以上、高基数列(如 user_id)有效;用错场景或写法反而比 COUNT(DISTINCT) 更慢、更不准。
APPROX_COUNT_DISTINCT 能快多少、为什么快
它不建全量哈希表,而是用 HyperLogLog++ 算法维护一个固定约 12KB 的 sketch 结构。无论输入是 100 万还是 10 亿行,内存和耗时几乎不变。而 COUNT(DISTINCT) 在 5 亿不同 user_id 时,哈希表可能占数 GB 内存,极易触发磁盘 spill,执行时间翻倍甚至超时。
官方保证:97% 概率下误差 ≤2%。比如真实值 1 亿,结果通常在 9800 万~1.02 亿之间——报表、DAU 监控、AB 实验够用,但别用于财务对账。
以下情况它不会快:
-
user_id只有几十个取值(低基数),APPROX_COUNT_DISTINCT反而多一层算法开销 - 表只有几万行,I/O 和解析开销已远超聚合本身
- 字段含大量
NULL,虽自动忽略,但若 WHERE 条件没过滤干净,仍会白扫无效数据
SQL Server 中必须注意的语法与兼容性
函数名严格为 APPROX_COUNT_DISTINCT(带下划线,大小写不敏感),仅支持 SQL Server 2019 及以上版本,且数据库兼容级别 ≥ 150:
ALTER DATABASE [YourDB] SET COMPATIBILITY_LEVEL = 150;
不支持第二个参数(如误差精度),传了会报错。也不能用于 image、ntext、sql_variant 等类型,对 varchar(max) 或 json 字段需先 CAST 成普通字符串。
常见翻车点:
- 误写成
APPROX_COUNT_DISTINCT()空括号 → 报错 “An aggregate may not appear in the set list of an UPDATE statement” - 在 SQL Server 2017 或更低版本执行 → 报错 “'APPROX_COUNT_DISTINCT' is not a recognized built-in function name”
- 字段是
datetime2但业务上想按天去重 → 忘记先CAST(oper_time AS DATE),导致基数虚高、误差放大
WHERE 条件和索引不配,再快的函数也白搭
函数快,不代表整条查询快。如果 WHERE 导致全表扫描,APPROX_COUNT_DISTINCT 就是在“近似地扫全表”。
典型错误:
-
WHERE DATE(oper_time) = '2026-09-10'→ 时间索引失效,强制计算每行函数 - 只在
(user_id)上建索引,但查询带WHERE biz_type = 'login' AND oper_time >= '2026-09-01'→ 无法覆盖,仍要回表
正确做法:
- 把时间条件写成可走索引的形式:
WHERE oper_time >= '2026-09-10' AND oper_time - 建联合索引:
CREATE INDEX IX_biz_time_uid ON events(biz_type, oper_time, user_id) - 高频统计场景,直接预聚合:
INSERT INTO daily_dau SELECT '2026-09-10', biz_type, APPROX_COUNT_DISTINCT(user_id) FROM events WHERE dt = '2026-09-10' GROUP BY biz_type
GROUP BY 中混用精确与近似聚合,小心执行计划降级
在同一 SELECT 中同时写 COUNT(*) 和 APPROX_COUNT_DISTINCT(user_id),某些 SQL Server 版本会放弃优化路径,退回到全哈希聚合模式,失去所有性能优势。
更隐蔽的问题是窗口函数场景:
-
COUNT(DISTINCT user_id) OVER (PARTITION BY region)→ SQL Server 不支持,直接报错 - 强行改写为
APPROX_COUNT_DISTINCT(user_id) OVER (PARTITION BY region)→ 虽语法通过,但每个分区都独立初始化 sketch,内存占用随分区数线性增长,极易 OOM
替代方案:
- 先按
region分组聚合近似值:SELECT region, APPROX_COUNT_DISTINCT(user_id) FROM events GROUP BY region - 若需窗口内累计去重(如滚动 DAU),改用
COLLECT_SET(user_id) OVER (PARTITION BY region ORDER BY dt ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),但必须确保user_id是枚举有限或已做采样,否则集合爆炸
真正容易被忽略的一点:APPROX_COUNT_DISTINCT 的误差不是均匀分布的,当数据存在明显时间衰减(比如新用户激增、老用户沉默)或地域倾斜(某省占 70% 流量)时,误差可能局部突破 2%,上线前务必用最近 3 天抽样数据人工比对偏差趋势。

















