sum(distinct col)会触发数据倾斜,因其需为每个col值维护独立聚合状态,高频值(如'unknown'占40%)导致所有含该值的行被路由至同一reducer,造成单点过载;且hive/spark中该操作强制全局shuffle,无法map端预聚合,多distinct表达式还引发重复shuffle。

为什么 SUM(DISTINCT col) 会触发数据倾斜
因为 SUM(DISTINCT col) 不是“先去重再求和”的线性过程,而是数据库必须为每个 col 值维护独立的聚合状态——当某个值高频出现(比如 user_id = 'unknown' 占全量 40%),所有含该值的行都会被路由到同一个 reducer 或执行单元,其他节点空闲,形成典型倾斜。
SUM(DISTINCT) 在 Hive/Spark 中的实际执行路径
Hive 默认对每个 COUNT(DISTINCT) 或 SUM(DISTINCT) 启动独立的 MapReduce 阶段;多个 DISTINCT 表达式(如 SUM(DISTINCT a), COUNT(DISTINCT b))不会复用中间结果,导致 shuffle 数据翻倍、磁盘 IO 暴增。
-
hive.groupby.skewindata=true对SUM(DISTINCT)无效——它只优化GROUP BY+ 聚合,不覆盖 DISTINCT 聚合函数 -
hive.map.aggr=true可在 Map 端预去重,但仅对低基数col有效;若col基数高(如千万级 user_id),Map 端哈希表仍会 OOM - Spark SQL 中,
SUM(DISTINCT)强制触发Exchange+ 全局 shuffle,无法被map-side combine优化
比 SUM(DISTINCT) 更稳的替代写法
真正要的是“每个 key 的某字段去重后求和”,语义上等价于先按 key 分组、再对组内字段去重求和——这必须拆成两层:外层聚合内层结果。
- 正确逻辑:
SELECT SUM(total_per_key) FROM (SELECT key, SUM(DISTINCT amount) AS total_per_key FROM t GROUP BY key) t2——但注意:MySQL 不支持内层SUM(DISTINCT),PostgreSQL 支持但性能差 - 通用解法:改用
GROUP BY key, dedup_col去重后再聚合,例如:SELECT key, SUM(amount) FROM (SELECT DISTINCT key, dedup_col, amount FROM t) t2 GROUP BY key - 高基数场景直接放弃精确计算:
approx_sum_distinct(col)(Trino)、hll_union_agg(hll_hash(col))(Spark 3.4+)误差可控,耗时降为 1/5
容易被忽略的索引陷阱
建了 INDEX(col) 对 SUM(DISTINCT col) 几乎没用——DISTINCT 聚合依赖哈希或排序去重,不是点查;只有当查询能转成 GROUP BY col 且索引覆盖全部 SELECT 字段时,才可能触发 Loose Index Scan。
- 想加速
SELECT SUM(DISTINCT col) FROM t WHERE dt = '2026-08-25',优先加分区剪枝,再建INDEX(dt, col)联合索引(顺序不能反) - 如果
col是字符串且长度波动大,MySQL 会截断索引前缀,默认只索引前 767 字节,导致实际去重范围变小、结果不准











