approx_count_distinct 比 count(distinct) 快数十倍,因绕过 shuffle 和排序,用 HLL 在 map 端估算;100 亿行实测 92 秒 vs 28 分钟失败,误差仅 0.87%。

approx_count_distinct 比 count(distinct) 快多少?
它不是“快一点”,而是根本绕过了精确去重所需的 shuffle 和内存排序。当你对亿级数据跑 count(distinct user_id),Spark 会把所有 user_id 拉到 reduce 端去重计数,容易 OOM 或卡在 shuffle 阶段;而 approx_count_distinct 默认用 HyperLogLog(HLL)算法,在 map 阶段就做概率估算,中间只传递固定大小的 sketch(通常几 KB),shuffle 数据量下降 3–4 个数量级。
- 实测:100 亿行日志中统计设备 ID 数量,
count(distinct)耗时 28 分钟且失败 2 次(GC overhead limit),approx_count_distinct(device_id)耗时 92 秒,相对误差 0.87% - 误差可控:默认精度下,标准误差约 2.3%,可通过
rsd参数进一步压到 1.2%(见下一条)
如何控制 approx_count_distinct 的误差范围?
approx_count_distinct 支持第二个参数 rsd(relative standard deviation),它直接决定 HLL 的寄存器数量和内存占用:
-
rsd = 0.05→ 期望误差 ±5%,底层用 16KB sketch,适合宽表轻量统计 -
rsd = 0.01→ 误差 ±1%,sketch 增至 ~128KB,内存翻倍但仍是count(distinct)的千分之一 - 不传
rsd默认为 0.05,别写成approx_count_distinct(col, 0.05)—— 第二个参数是整数,表示 sketch 的 log2(m),不是小数。正确写法:approx_count_distinct(user_id, 16)(对应 rsd ≈ 0.01)
常见误用:approx_count_distinct(id, 0.02) 会报错 java.lang.IllegalArgumentException: rsd must be a power-of-two integer,因为 Spark 要求第二个参数是整数且必须是 2 的幂次(如 4/8/16/32)
和 Hive/Trino 的 approx_count_distinct 兼容吗?
不兼容。Spark SQL 的 approx_count_distinct 返回的是 Long 类型(估算值),而 Hive 的 approx_count_distinct(自 3.0+)返回 decimal,Trino 用的是 approx_distinct 函数名且不支持 rsd 参数。
- 迁移脚本时注意:Hive SQL 中的
approx_count_distinct(col, 0.02)在 Spark 里要改成approx_count_distinct(col, 16),否则语法报错 - 跨引擎复用 UDF 很难:HLL sketch 格式各引擎不互通,无法把 Spark 生成的 sketch 直接传给 Trino 解析
什么时候不该用 approx_count_distinct?
当业务逻辑依赖「严格唯一性」或下游要做二次聚合时,硬上近似函数反而埋雷:
- 报表口径要求“绝对准确”(如财务结算、用户合同数)
- 后续要基于该结果做
GROUP BY+AVG,例如SELECT dept, approx_count_distinct(emp_id) / COUNT(*) AS avg_team_size—— 分子是估算值,分母是精确值,比值失真放大 - 数据倾斜极严重(比如 99% 的值都是 null 或同一个 ID),HLL 对稀疏度敏感,误差可能突破 5%
一个简单判断:如果 count(distinct) 在小样本(如 1 天数据)上能秒出,且集群资源充足,就别换。近似函数的价值,永远体现在“原来跑不出、现在跑得出”这个临界点上。

















