count(*) over (partition by key) 会加剧数据倾斜,因其按 partition by 字段 shuffle,使所有相同 key(如“0”或空值)记录集中到同一 reducer,导致单点负载过重、任务卡顿或 OOM。

为什么 count(*) over (partition by key) 会加剧数据倾斜
直接对倾斜 key(比如用户ID为“0”或空字符串)开窗计数,会让所有含该 key 的记录被拉到同一个 reducer,因为 Hive 的窗口函数默认按 partition by 字段做 shuffle —— 这和 group by 倾斜本质相同,但更隐蔽:你没写 group by,却触发了单点聚合。
常见错误现象:Execution failed with exit status 2、任务卡在 99%、GC overhead limit exceeded,日志里能看到某个 reducer 处理记录数是其他 reducer 的百倍以上。
- 别用
count(*) over (partition by key)统计高频key出现次数 - 如果必须统计,先用
group by key+mapjoin预聚合,再关联回原表 - Hive 3.1+ 支持
count(*) over (partition by key rows between unbounded preceding and unbounded following),但不解决 shuffle 热点,仅改变计算时机
用 row_number() + 盐值打散倾斜 key
对已知倾斜 key(如 key = '0' 或 key is null),在 partition by 时混入随机因子,把单个热点分散到多个分区,再二次聚合。核心不是避免倾斜,而是「可控地分摊」。
实操示例:给每个倾斜 key 加 rand() % 10 作为盐值,非倾斜 key 保持原样:
SELECT
key,
COUNT(*) AS cnt,
SUM(val) AS sum_val
FROM (
SELECT
CASE
WHEN key IN ('0', '') OR key IS NULL THEN CONCAT(key, '_', CAST(FLOOR(RAND() * 10) AS STRING))
ELSE key
END AS salted_key,
val
FROM src_table
) t
GROUP BY salted_key;注意:RAND() 在 map 阶段稳定(Hive 中 map-side rand 不重算),但不能在 reduce 阶段依赖其一致性;盐值范围(如 % 10)需根据倾斜程度调整,太小仍热,太大增加小文件和 join 开销。
sum() over () 类全量窗口函数在倾斜场景下的替代写法
想算每个 key 占全局总和的比例?别直接写 sum(val) over () / sum(val) over (partition by key) —— 全局窗口(over ())本身不倾斜,但一旦和倾斜的 partition by key 混用,中间结果膨胀严重,容易 OOM。
正确路径是两阶段:先用 mapred.reduce.tasks 显式控制 reduce 数量,再用子查询分离计算:
- 第一步:用
SELECT sum(val) FROM src_table算出全局sum_val,结果必为单行,可广播为mapjoin - 第二步:用
SELECT key, sum(val) AS key_sum FROM src_table GROUP BY key聚合各key,加DISTRIBUTE BY RAND()缓解 reducer 不均 - 最后
mapjoin关联两者,避免 shuffle
这样绕开了窗口函数,也规避了 sum() over () 和 sum() over (partition by key) 同时触发 shuffle 的叠加风险。
Hive 版本与配置对窗口函数倾斜的实际影响
Hive 2.3 之前不支持 ROWS BETWEEN 精确帧定义,只能靠 RANGE(易误触发全排序);Hive 3.0+ 引入 hive.optimize.windowing(默认 true),会对简单窗口自动转为 GROUP BY + JOIN,但对含倾斜 key 的语句可能失效。
关键配置项:
-
hive.groupby.skewindata=true:仅作用于GROUP BY,对窗口函数无效 -
hive.auto.convert.join=true+ 小表广播:是你在窗口后做关联时最可靠的兜底手段 -
hive.exec.reducers.bytes.per.reducer:调低(如设为64000000)可强制增加 reducer 数,缓解单点压力,但对窗口函数效果有限
真正起效的永远是业务逻辑拆分:识别出那几个占 80% 流量的 key,单独拎出来处理,比调参可靠得多。

















