订阅更新

COUNT(DISTINCT) 数据量很大时怎么优化?

面试问 116 约 2 分钟读完

本文目录
  1. 面试怎么回答
  2. 先确认是否必须精确
  3. 精确场景分阶段聚合
  4. 复用场景沉淀中间结果
  5. 最后才看引擎参数
  6. 为什么不能只回答概念?
  7. 落地时我会检查什么?
  8. 一个典型场景

面试怎么回答

我会先确认是否必须精确。允许误差的监控类指标可用 HLL 一类基数估计;精确场景则按分区或盐值局部去重,再做全局汇总,并处理热点键。若同一集合被反复使用,会沉淀日级去重明细、Bitmap 或可合并草图。最后再结合分区裁剪、并行度和内存调参,而不是指望改写 COUNT 语法解决高基数问题。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

一条 COUNT(DISTINCT user_id),数据从千万涨到百亿后,突然变成整条链路最慢的算子。

把内存调大只能延后问题,因为精确去重本质上要汇聚大量唯一值。

优化 DISTINCT 不能只盯 SQL 语法,要在精确性、基数、Shuffle、复用频率和可接受误差之间做选择。

先确认是否必须精确

财务结算、权益发放通常要求精确;趋势监控、实时大盘可能接受近似值。若允许误差,可使用 HyperLogLog 等基数估计算法,用固定空间换取可控误差。

精确场景分阶段聚合

可以先按分区或盐值做局部去重,再汇总全局唯一值,减少一次性压力。多个维度同时 DISTINCT 时,应避免把完全不同的去重集合强行塞进同一聚合阶段。

去重方案选择

复用场景沉淀中间结果

同一用户集合被多张报表重复计算时,可以沉淀日级去重明细、Bitmap 或基数草图。选择哪种结构,要看是否需要集合交并差、跨天合并和精确回查。

最后才看引擎参数

分区裁剪、列裁剪、合理并行度和数据倾斜处理仍然重要,但它们无法改变唯一值规模。先判断算法和数据结构,再调 Shuffle 分区与内存。

为什么不能只回答概念?

“误区:换一种写法就能消除去重成本”之所以常见,是因为它在理想数据和单次执行里往往说得通。但生产环境会加入数据规模、异常值、并发、版本、失败重试和下游口径,这些条件一变化,只记住术语就很容易给出错误结论。

所以我会把 先确认是否必须精确、精确场景分阶段聚合、复用场景沉淀中间结果、最后才看引擎参数 串成一条完整链路。最终判断标准不是某个函数或配置是否出现,而是能否稳定得到可解释、可验证、可恢复的业务结果。

落地时我会检查什么?

  • 输入边界:确认数据范围、时间口径、主键和异常值是否符合假设。
  • 执行过程:观察分区、并行度、状态、Shuffle 或资源变化,验证方案是否真正生效。
  • 输出结果:用行数、唯一性、关键指标和样本明细证明结果正确,而不是只看任务成功。
  • 失败恢复:说明重跑、回滚、版本兼容和责任人,避免一次成功后无人维护。

这些检查不一定全部写进一条 SQL 或一个任务,但面试时能主动补充,说明你回答的不是背诵题,而是在描述一套可上线的工程方案。

一个典型场景

实际工作中常见的是同时计算新增用户、活跃用户和付费用户,三个 DISTINCT 混在一条宽 SQL 中。每个集合的过滤条件不同,执行计划产生多轮重分布。拆成可复用的用户集合并按场景选择精确明细与 HLL 草图后,计算链路更清晰,也更容易验证误差。

先决定精确还是近似,再决定聚合链路和存储结构。

刷题进度保存在当前浏览器