APPROX_COUNT_DISTINCT 是 SQL Server 2019 引入的近似去重计数函数,适用于千万行以上、高基数列且可容忍 ≤2% 误差的场景,基于 HyperLogLog 算法实现,内存占用仅为 COUNT(DISTINCT) 的 5–10%,但要求兼容级别 ≥150,不支持窗口语法,且对低基数数据无加速优势。

APPROX_COUNT_DISTINCT 在 SQL Server 2019 中能显著提速,但前提是数据量够大(千万行以上)、去重列基数高、且业务可接受 ≤2% 的误差。 它不是 COUNT(DISTINCT) 的“无脑替换”,而是一个有明确适用边界的性能优化工具。
什么时候用 APPROX_COUNT_DISTINCT 才真快
它快,是因为跳过了精确哈希去重所需的完整内存哈希表构建和排序归并——改用 HyperLogLog 的概率算法,内存占用通常只有 COUNT(DISTINCT) 的 5–10%。但这个优势只在以下场景兑现:
- 单次查询扫描行数 ≥ 500 万,且
expression列的 distinct 值数量 ≥ 百万级(比如用户 ID、商品 SKU) - 执行计划里看到
COUNT(DISTINCT)出现了 “spill to disk”(写入 tempdb),或内存授予(Memory Grant)远超 1GB - 查询跑在资源受限的 Azure SQL 数据库 tier(如 S2/S3),或 SQL Server 2019 实例开启了轻量级查询内存限制(
MAX_GRANT_PERCENT) - 你正在做实时看板或 ETL 预聚合,对“14,823,601 vs 14,970,244”这种量级差异不敏感
SQL Server 2019 中必须检查的兼容性前提
这个函数在 SQL Server 2019 中默认可用,但若数据库是从旧版本升级而来,COMPATIBILITY_LEVEL 可能仍为 140(SQL Server 2017)或更低,此时调用 APPROX_COUNT_DISTINCT 会直接报错:
Msg 104171, Level 15, State 1, Line 1<br>‘APPROX_COUNT_DISTINCT’ is not a recognized built-in function name.
确认并升级兼容级别:
SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME();<br>ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 150;
- 150 对应 SQL Server 2019;160 是 2022,也支持该函数,但没必要为它升
- 升级后无需重启服务,但会影响整个数据库的查询优化器行为(如启用自适应联接等),建议在维护窗口操作
- 注意:Azure SQL Database 默认已是高兼容级别,一般不用手动设
别踩这些实际使用坑
很多人一换就出问题,不是函数不行,是没看清约束:
-
APPROX_COUNT_DISTINCT不接受image、ntext、text、sql_variant类型——哪怕列定义是nvarchar(max)也能用,但如果是老式ntext,必须先 CAST:APPROX_COUNT_DISTINCT(CAST(my_ntext_col AS nvarchar(max))) - 它自动忽略 NULL 值,这点和
COUNT(DISTINCT)一致,但如果你之前用COUNT(*) - COUNT(col)算 NULL 数,换成近似函数后逻辑要同步校验 - 不能嵌套在子查询的 SELECT 列表里再被外层用于 JOIN 或 WHERE(比如
WHERE approx_cnt > 1000),因为它是聚合函数,必须配 GROUP BY 或用在聚合上下文中;想过滤结果得用 HAVING 或外层 CTE - 在包含
ORDER BY+TOP的分页查询中,别指望它能替代COUNT(*) OVER()做总行数估算——它不支持窗口语法(SQL Server 版本不支持OVER子句),Databricks/Spark 才支持
对比验证时怎么测才靠谱
别只比“谁更快”,要验证误差是否在容忍范围内,且性能提升真实可测:
- 用
SET STATISTICS IO, TIME ON分别跑COUNT(DISTINCT)和APPROX_COUNT_DISTINCT,重点看逻辑读、CPU 时间、以及是否出现tempdbspill - 误差验证不要只查一个值:抽 3–5 个不同分组(比如按日期、地区),分别比对精确值和近似值,确认误差都在 ±2% 内(例如精确 1,000,000 → 近似 985,000~1,015,000)
- 如果业务要求“绝对不能少算”,注意 HyperLogLog 有轻微负偏差倾向(更可能低估),可提前乘 1.02 系数补偿(虽非标准做法,但在某些报表口径中被接受)
最常被忽略的一点:APPROX_COUNT_DISTINCT 的加速效果高度依赖数据分布。如果去重字段其实是低基维(比如只有几十个状态码),它不仅不快,还可能比 COUNT(DISTINCT) 更慢——因为算法初始化开销固定,小数据上纯属白耗 CPU。

















