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

本文涉及的产品
云数据库 PolarDB MySQL 版,列存表分析加速 8核16GB
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 本篇详解COUNT(DISTINCT)性能瓶颈与四大优化方案:① HyperLogLog(误差1–2%,极速);② 索引+GROUP BY加速精确统计;③ Bitmap(低基数场景);④ 预计算/物化视图。按精度、实时性、成本选型,告别半小时卡死!

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

上周我们讲了COUNT(*)在大表上的近似计数与HyperLogLog。这周继续聊COUNT的进阶话题——​去重计数​。你一定遇到过这样的需求:“查一下昨天的独立访客数”“统计这周活跃设备量”,直接用COUNT(DISTINCT user_id),10亿行表跑了半小时还没出结果,怎么办?

去重计数的两种场景

场景 需求 可接受误差
运营报表、趋势图 DAU、MAU 1-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) 固定报表、趋势图
物化视图 准实时(可刷新) 综合报表

优化决策树
deepseek_mermaid_20260603_1d174b.png


真实案例:某APP日活统计

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

如果要求精确,则采用T+1预计算:凌晨计算前一天的精确COUNT(DISTINCT device_id)存入MySQL,白天查询直接读结果。


总结

去重计数的优化没有“银弹”,关键在于根据业务对精度、实时性、成本的要求做出合理选择。HyperLogLog是误差容忍场景的利器,bitmap适合低基数,预计算适合固定报表。掌握了这些方案,你就能在“快”和“准”之间找到最佳平衡点。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~

相关文章
|
1月前
|
SQL 关系型数据库 MySQL
一张5000万行的表,加索引从45秒到0.02秒——索引设计你真的会吗
本文实测5000万订单表:无索引查询45秒,加索引后仅0.02秒(提升2250倍)。详解索引原理、建索引时机、联合索引最左前缀、覆盖索引及隐式转换陷阱,干货不啰嗦!
|
2月前
|
SQL 数据库 数据库管理
写完SQL先别跑,这两步能救你一晚
我是小耶,专注踩坑与填坑,今天分享SQL性能关键:数据库执行顺序(FROM→WHERE→…)与人脑思维的错位——切忌先JOIN后过滤!用实例对比,教你“过滤前置”提速技巧。养成自查习惯,SQL轻松快一倍!
|
2月前
|
SQL JSON 关系型数据库
慢SQL排查三板斧:SHOW PROCESSLIST + 慢查询日志 + EXPLAIN 实战
教你三招快速定位CPU 100%元凶:SHOW PROCESSLIST查活跃查询、开启慢日志+mysqldumpslow分析、EXPLAIN深度诊断SQL性能。干货不啰嗦,专治线上急症!
|
27天前
|
SQL 运维 关系型数据库
DBA必备技能:MySQL误删恢复完全指南(全量备份+binlog回放)
本文详解误删数据(如`DELETE FROM orders`)后的紧急恢复三步法:查Binlog→临时库回放→差异导回,并附4条血泪预防措施。不讲段子,只教能救命的操作!
|
2月前
|
SQL 数据库
多表关联查询入门:LEFT JOIN、INNER JOIN一文搞懂|转行学DB第6天
本文通俗易懂地讲解了数据库多表查询的三种JOIN操作:INNER JOIN(内连接)只返回两表匹配的数据,适用于查询交集数据;LEFT JOIN(左连接)保留左表所有记录并匹配右表数据,适用于查询主表完整信息;RIGHT JOIN(右连接)则保留右表所有记录。
|
2月前
|
SQL 关系型数据库 MySQL
主键、外键和约束:让数据库“有规矩”才能不出错!|转行学DB第5天
本文用通俗易懂的语言讲解了主键(数据的唯一标识)、外键(表间关联)以及唯一约束、非空约束等其他常见约束规则。通过具体SQL示例展示了各种约束的使用方法,并分享了新手容易踩的坑和实用建议。
|
2月前
|
SQL 人工智能 安全
AI圈开始“养马”了?聊聊龙虾退位、爱马仕登基
AI智能体“龙虾”(OpenClaw)的衰落与“爱马仕”(Hermes Agent)的崛起:前者因API限策与高危漏洞(CVSS 9.9)式微;后者以持久记忆、技能自生成、跨平台互通等实用能力破圈,成技术圈新“拐杖”。但技术无银弹,懂你的工具才是真助力。
|
12天前
|
SQL 存储 关系型数据库
覆盖索引:让你的查询直接从索引返回,彻底告别回表
覆盖索引是SQL优化中性价比较高的技巧,让查询直接从索引返回所需列,避免回表操作。本文解释覆盖索引的原理,通过EXPLAIN的“Using index”判断是否生效。结合复合索引设计、深分页优化(延迟关联)等场景,给出覆盖索引的使用方法和注意事项。用好覆盖索引,不改SQL逻辑,仅调整索引设计即可显著提升查询性能。
|
13天前
|
SQL 人工智能 关系型数据库
DBA的AI助手:向量检索与NL2SQL入门
本篇为DBA量身打造的AI入门指南:用最直白语言讲清向量检索(相似搜索、pgvector实战)与NL2SQL(自然语言写SQL)的本质、场景及落地路径。不卷算法,只讲DBA真正需要懂的数据库新能力——技术迭代快,但掌握关键点,你依然不可替代。
|
2月前
|
SQL 数据库 数据库管理
从运营到DBA,我用了这3个“偷懒”方法学SQL
用运营人思维教小白轻松学SQL:①把SQL当Excel对话,理解SELECT/FROM/WHERE;②建“报错翻译本”,快速定位解决错误;③用“填空题法”抄改练,复用模板上手。不求完美,先跑通、看懂、不崩溃!
从运营到DBA,我用了这3个“偷懒”方法学SQL