approx_count_distinct 比 count(distinct) 快,因其采用 hyperloglog++ 概率算法估算基数,仅需约 12kb 内存且性能稳定;而后者需全量去重建哈希表,内存随唯一值增长。

APPROX_COUNT_DISTINCT 为什么比 COUNT(DISTINCT) 快
因为它不真正去重,而是用 HyperLogLog++(HLL++)这类概率算法估算基数。COUNT(DISTINCT) 必须把所有非 NULL 值加载进内存建哈希表或排序,user_id 有 5 亿不同值,哈希表就可能占数 GB;而 APPROX_COUNT_DISTINCT 只维护一个约 12KB 的 sketch 结构,无论输入是 10 万行还是 10 亿行,内存和耗时几乎不变。
误差可控:默认相对标准偏差约 0.8–1.5%。比如真实 distinct 是 1 亿,结果通常在 9920 万~1.015 亿之间。对报表、监控、AB 实验等场景足够用,但别用于财务对账或唯一性校验。
不同数据库的函数名和参数差异
写错名字或传错参数会直接报错或返回 NULL,不是所有系统都叫 APPROX_COUNT_DISTINCT:
- Oracle:原生支持
APPROX_COUNT_DISTINCT(user_id),不接受误差参数,忽略 NULL,返回NUMBER - PostgreSQL:需先安装
hll扩展,常用写法是hll_cardinality(hll_add_agg(user_id)),或封装为approx_count_distinct(user_id) - Spark SQL / Databricks:
approx_count_distinct(col, relativeSD),relativeSD可设 0.01(更准)到 0.1(更快),不设则用默认 0.024 - ClickHouse:
uniq(user_id)是近似版(HLL),uniqExact(user_id)才是精确版(慎用,易 OOM) - MySQL 8.0+:原生支持
APPROX_COUNT_DISTINCT(user_id),但不支持调精度,也不能传第二个参数
WHERE 条件写错会让 APPROX_COUNT_DISTINCT 白费力气
函数再快,也救不了低效的过滤逻辑。常见翻车点:
-
WHERE DATE(oper_time) = '2026-08-11'→ 时间索引失效,全表扫描后再算近似值,IO 耗尽性能 - 索引只建了
(user_id),但查询带WHERE biz_channel = 'pay' AND oper_time >= '2026-08-01'→ 无法覆盖,仍要回表或扫全索引 - 正确做法:建联合索引
(biz_channel, oper_time, user_id),让 WHERE 和聚合字段一起被覆盖 - 更进一步:把时间条件下推到预聚合层,比如每天跑一次
INSERT INTO daily_stats SELECT biz_channel, approx_count_distinct(user_id) FROM raw WHERE dt = '2026-08-11' GROUP BY biz_channel
多个近似去重同时出现,别以为能自动复用 sketch
每个 APPROX_COUNT_DISTINCT 调用都独立初始化 sketch,不会共享中间状态。例如:
SELECT approx_count_distinct(user_id), approx_count_distinct(device_id), approx_count_distinct(ip) FROM events;
这会分别构建 3 个 HLL 结构,内存开销 ≈ 3 × 12KB,且无法复用扫描过程。如果业务允许,优先考虑单指标聚合 + 多次查询,或改用物化视图/预计算表。
最常被忽略的一点:APPROX_COUNT_DISTINCT 对 NULL 完全忽略,这点和 COUNT(DISTINCT) 一致;但如果你的列本身 NULL 率高,又没加 WHERE col IS NOT NULL,实际参与估算的数据量可能远低于预期——这个偏差不会体现在错误里,只会悄悄拉低结果。











