COUNT进阶续:超大表去重计数极致优化

作者:技术拾光

时间:2026-06-04

浏览:157

针对大数据表去重计数缓慢,可根据精度容忍度选择优化方案:可接受1-2%误差时采用HyperLogLog(内存小、速度快);要求精确时通过覆盖索引、GROUPBY子查询或分桶优化;低基数场景可用bitmap聚合;固定维度可用预计算或物化视图以空间换时间。

文章目录

上周聊了 COUNT(*) 在大表上的近似计数与 HyperLogLog,这周继续深入 COUNT 的进阶话题——去重计数。你是不是也遇到过这样的场景:想查一下昨天的独立访客数,或者统计本周的活跃设备量,一个 COUNT(DISTINCT user_id) 下去,10亿行的表跑了半小时还没出来?这种时候,硬等肯定不是办法。

其实,去重计数的优化,关键还是要看你的业务场景对精度的容忍度有多高。我们先来快速划分一下:

场景需求可接受误差
运营报表、趋势图DAU、MAU1-2%
财务、库存、对账精确金额、订单数0%

下面按误差从大到小,聊聊几种主流的优化手段。


方案一:近似去重(HyperLogLog)—— 要快,误差1-2%能接受

HyperLogLog,说白了就是一种概率算法,用极其固定的内存(大概16KB)去估算去重元素的个数。背后的逻辑其实挺有意思的:它把每个元素哈希一下,然后统计哈希值二进制表示里前导零的最大长度,通过这个长度来反推去重总数。内存占用小,速度极快,误差又可控,简直是这类场景里的利器。

适用场景: UV、DAU、独立IP、搜索词去重统计等,只要不涉及真金白银,基本都能用。

实现方式:

  1. Redis HyperLogLog(最常用)
import redis
r = redis.Redis()
for user_id in logs:
    r.pfadd("daily_uv:2026-06-02", user_id)
uv = r.pfcount("daily_uv:2026-06-02")  # 误差1%以内
  1. PostgreSQL + hll扩展
CREATE EXTENSION hll;
SELECT hll_cardinality(hll_add_agg(hll_hash_integer(user_id))) FROM logs;

方案二:精确去重,但用索引优化 —— 必须准,也要尽量快

如果误差一丁点都不能有,那我们就得在索引设计上动脑筋,尽量少扫描数据。

技巧1:覆盖索引

COUNT(DISTINCT user_id) 其实只需要 user_id 这一列。如果 (user_id) 上有索引,InnoDB 可以直接去扫索引而不是全表,I/O 开销能降不少。

-- 确保user_id有索引
CREATE INDEX idx_user_id ON logs(user_id);
SELECT COUNT(DISTINCT user_id) FROM logs;

技巧2:GROUP BY 代替 DISTINCT

在一些数据库里,用 GROUP BY 做子查询,外层再 COUNT(*),有时会比直接的 COUNT(DISTINCT) 更快,具体得看优化器的脾气。

SELECT COUNT(*) FROM (SELECT user_id FROM logs GROUP BY user_id) t;

实测对比(1000万行,user_id有索引):

写法耗时
COUNT(DISTINCT user_id)12秒
GROUP BY子查询11秒(差异不大)

技巧3:分桶计数

如果数据分布比较均匀,可以按某个维度分桶,每个桶分别去重计数,最后再求和。比如按日期分区,每天分别做 COUNT(DISTINCT) 再累加,前提是桶间没有重复数据。


方案三:bitmap聚合 —— 极速精确去重(限低基数场景)

如果去重的列基数很低,比如性别、状态、类型这种只有几个值的字段,那 bitmap 就是神器。每个值对应一个 bit 位,多个值之间做 OR/AND 操作,快得飞起。

实现方式: 用 PostgreSQL 的 bitmap 扩展,或者 MySQL 的 SET 类型都可以。

适用场景: 标签系统、权限判断、漏斗分析里的“是否完成某动作”这类判断。


方案四:预计算/物化视图 —— 以空间换时间

如果去重的维度是固定的,比如每日 DAU,那就提前算好存下来,查询时直接读结果,省时省力。

实现方式:

  • 每日定时任务,算好前一天的 COUNT(DISTINCT user_id),写入统计表
  • 或者用物化视图(PostgreSQL 原生支持,MySQL 需要借助第三方工具)
方案实时性存储成本适用场景
实时COUNT(DISTINCT)实时低小表或低频查询
HyperLogLog实时极低可接受误差的高频查询
预计算表非实时(T+1)中固定报表、趋势图
物化视图准实时(可刷新)中综合报表

优化决策树


真实案例:某APP日活统计

  • 数据量:每日约5000万独立设备ID
  • 要求:实时展示当天DAU,可接受1%误差
  • 方案:用 Redis HyperLogLog,每条日志 pfadd,实时 pfcount
  • 结果:内存占用约12KB/天,响应时间 <10ms,误差 <1.5%

如果要求绝对精确,那就走 T+1 预计算:凌晨算好前一天精确的 COUNT(DISTINCT device_id) 存入 MySQL,白天查直接读结果。


总结

去重计数的优化,从来没有什么“银弹”。关键还是根据业务对精度、实时性和成本的要求,做出最合理的取舍。HyperLogLog 是误差容忍场景下的利器,bitmap 适合低基数,预计算适合固定报表。把这些方案都装进工具箱,你就能在“快”和“准”之间,找到那个最佳的平衡点。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章