COUNT(DISTINCT) 数据量很大时怎么优化?
面试怎么回答
我会先确认是否必须精确。允许误差的监控类指标可用 HLL 一类基数估计;精确场景则按分区或盐值局部去重,再做全局汇总,并处理热点键。若同一集合被反复使用,会沉淀日级去重明细、Bitmap 或可合并草图。最后再结合分区裁剪、并行度和内存调参,而不是指望改写 COUNT 语法解决高基数问题。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
一条 COUNT(DISTINCT user_id),数据从千万涨到百亿后,突然变成整条链路最慢的算子。
把内存调大只能延后问题,因为精确去重本质上要汇聚大量唯一值。
优化 DISTINCT 不能只盯 SQL 语法,要在精确性、基数、Shuffle、复用频率和可接受误差之间做选择。
先确认是否必须精确
财务结算、权益发放通常要求精确;趋势监控、实时大盘可能接受近似值。若允许误差,可使用 HyperLogLog 等基数估计算法,用固定空间换取可控误差。
精确场景分阶段聚合
可以先按分区或盐值做局部去重,再汇总全局唯一值,减少一次性压力。多个维度同时 DISTINCT 时,应避免把完全不同的去重集合强行塞进同一聚合阶段。

复用场景沉淀中间结果
同一用户集合被多张报表重复计算时,可以沉淀日级去重明细、Bitmap 或基数草图。选择哪种结构,要看是否需要集合交并差、跨天合并和精确回查。
最后才看引擎参数
分区裁剪、列裁剪、合理并行度和数据倾斜处理仍然重要,但它们无法改变唯一值规模。先判断算法和数据结构,再调 Shuffle 分区与内存。
为什么不能只回答概念?
“误区:换一种写法就能消除去重成本”之所以常见,是因为它在理想数据和单次执行里往往说得通。但生产环境会加入数据规模、异常值、并发、版本、失败重试和下游口径,这些条件一变化,只记住术语就很容易给出错误结论。
所以我会把 先确认是否必须精确、精确场景分阶段聚合、复用场景沉淀中间结果、最后才看引擎参数 串成一条完整链路。最终判断标准不是某个函数或配置是否出现,而是能否稳定得到可解释、可验证、可恢复的业务结果。
落地时我会检查什么?
- 输入边界:确认数据范围、时间口径、主键和异常值是否符合假设。
- 执行过程:观察分区、并行度、状态、Shuffle 或资源变化,验证方案是否真正生效。
- 输出结果:用行数、唯一性、关键指标和样本明细证明结果正确,而不是只看任务成功。
- 失败恢复:说明重跑、回滚、版本兼容和责任人,避免一次成功后无人维护。
这些检查不一定全部写进一条 SQL 或一个任务,但面试时能主动补充,说明你回答的不是背诵题,而是在描述一套可上线的工程方案。
一个典型场景
实际工作中常见的是同时计算新增用户、活跃用户和付费用户,三个 DISTINCT 混在一条宽 SQL 中。每个集合的过滤条件不同,执行计划产生多轮重分布。拆成可复用的用户集合并按场景选择精确明细与 HLL 草图后,计算链路更清晰,也更容易验证误差。
先决定精确还是近似,再决定聚合链路和存储结构。