APPROX_COUNT_DISTINCT 仅在表行数≥1000万、去重值≥100万、字段高基数且无大量NULL、索引覆盖查询条件时才比COUNT(DISTINCT)快而准,适用于DAU/UV等容忍≤2%误差的场景,严禁用于财务审计等需精确值的场合。

APPROX_COUNT_DISTINCT 在 SQL Server 2019 中只对千万行以上、高基数列有效;用错场景(比如小表、低基数、NULL多、索引没配好)反而比 COUNT(DISTINCT) 更慢更不准。
什么时候用 APPROX_COUNT_DISTINCT 才真能快
它快,不是因为“函数本身快”,而是因为它绕过了哈希去重的内存爆炸问题。前提是:数据量够大、列值足够分散、查询路径干净。
- 表行数 ≥ 1000 万,且目标列(如
user_id)实际去重后 ≥ 100 万——这是 HyperLogLog++ 算法发挥优势的下限 - 字段不含大量
NULL,或已用WHERE col IS NOT NULL显式过滤,否则白扫无效行 - 不用于财务、对账、审计等要求精确值的场景;误差 ≤2% 是统计保证,不是确定性结果
- 真实值 1 亿时,结果大概率落在 9800 万~1.02 亿之间——DAU、UV 趋势监控够用,但别当 COUNT 去用
为什么写了 APPROX_COUNT_DISTINCT 还是慢
函数快 ≠ 查询快。常见卡点全在 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 < '2026-09-11',并建联合索引(biz_type, oper_time, user_id) - 如果字段是
datetime2但业务按天统计,必须先CAST(oper_time AS DATE),否则时间精度拉高基数,误差放大
语法和兼容性踩坑清单
报错往往不是逻辑问题,而是版本或写法越界。SQL Server 对这个函数很严格。
- 数据库兼容级别必须 ≥ 150:
ALTER DATABASE [YourDB] SET COMPATIBILITY_LEVEL = 150 - 仅支持 SQL Server 2019 及以上;2017 或更低版本执行会报
'APPROX_COUNT_DISTINCT' is not a recognized built-in function name' - 函数名必须带下划线:
APPROX_COUNT_DISTINCT,写成APPROX_COUNT_DISTINCT()(空括号)会触发语法错误 - 不支持
image、ntext、sql_variant;对varchar(max)或 JSON 字段,需先CAST(col AS VARCHAR(8000)) - 不接受第二个参数(如误差精度),传了直接报错;Azure Databricks 支持
relativeSD,但 SQL Server 不行
和 COUNT(DISTINCT) 对比的实际选择建议
别默认“新函数就更好”。该用哪个,取决于你手上的数据和需求边界。
- 表只有几万行?直接用
COUNT(DISTINCT),I/O 和解析开销远大于聚合差异 -
user_id实际只有几十个取值(比如内部系统员工 ID)?低基数下APPROX_COUNT_DISTINCT多一层算法调用,纯属加开销 - 需要精确值(比如生成发票明细、合规报表)?必须用
COUNT(DISTINCT),别碰近似函数 - 查的是
COUNT(DISTINCT user_id) FILTER (WHERE status = 'active')?SQL Server 不支持FILTER子句,得改写为COUNT(DISTINCT CASE WHEN status = 'active' THEN user_id END)
最易被忽略的一点:APPROX_COUNT_DISTINCT 的误差是概率性的,不是可控的。你无法通过加 hint、调参数让它更准——它设计上就放弃精确性来换资源效率。上线前务必用真实数据集跑对比测试,确认误差范围是否在业务容忍内。

















