hyperloglog比count(distinct)更省内存,因其仅用固定1.5kb寄存器数组实现o(1)空间复杂度,而后者需o(ndv)内存存储所有唯一值;误差约±1.6%,适用于uv统计等允许小幅误差的场景。

HyperLogLog 为什么比精确 COUNT(DISTINCT) 更省内存
因为 COUNT(DISTINCT) 要求数据库在内存中维护所有唯一值的哈希集合(或排序后去重),当去重后基数达百万级,仅存储哈希值就可能占用百 MB 以上内存;而 HyperLogLog 只需固定大小的寄存器数组——典型实现仅用 1.5KB 内存就能估算高达 10^9 量级的基数,空间复杂度是 O(1),不是 O(NDV)。
BigQuery / PostgreSQL 等引擎里 HLL 是怎么被调用的
它不直接暴露为用户手写的函数,而是由查询优化器在满足条件时自动启用:
- 当列的统计信息(如
n_distinct)预估基数远超内存阈值(例如 > 100K),且查询无ORDER BY或强一致性要求时,优化器可能改用 HLL 估算路径 - BigQuery 中显式使用
APPROX_COUNT_DISTINCT(column)会强制走 HLL;PostgreSQL 的hll扩展则需手动建hll_hash_bigint()+hll_add()+hll_cardinality() - 注意:HLL 不支持
WHERE条件下动态裁剪——它必须先对整列哈希,再合并寄存器,所以带高选择性过滤的场景反而可能不如物化中间结果快
HLL 的误差和适用边界在哪
标准 HLL 相对误差约 1.04 / sqrt(m)(m 是寄存器个数),常见实现取 m = 2^14,误差约 ±1.6%。但它对小基数不友好:
- 当真实 NDV
- 某些引擎(如 Google PowerDrill 改进版)会叠加 MinCount 阶段,在 NDV
- 如果你的业务报表要求“UV 必须等于 9997 而不是 ≈10000”,那 HLL 就不该出现在最终交付 SQL 里——它只适合探索、监控、ETL 中间层
为什么不能简单把 HLL 当成 COUNT(DISTINCT) 的替代品
最常被忽略的一点:HLL 结果不可逆、不可拆分。你无法从一个 hll_cardinality(hll_col) 值反推出哪些 user_id 被计入,也无法用它做 GROUP BY region HAVING COUNT(DISTINCT user_id) > 1000 这类带阈值的过滤——因为 HLL 寄存器本身不保留原始值,只保留概率特征。
换句话说,HLL 解决的是“大概有多少”,而不是“有哪些、是否达标”。真要保精度或做后续逻辑,还是得回到物化唯一值或采样+校正的老路。










