ROW_NUMBER()在Hive中OOM是因为按PARTITION BY全量shuffle且不支持局部排序归并,倾斜key导致reducer内存超限;需用pmod(hash(key),N)打散分组并两阶段重排序号。

ROW_NUMBER() 为什么在 Hive 里一用就 OOM
因为 ROW_NUMBER() OVER (PARTITION BY key ORDER BY ...) 在 Hive 中默认按 PARTITION BY 字段全量 shuffle,不支持局部排序+两阶段归并。如果某个 key 占比超 10%,对应 reducer 就会收到远超均值的数据,而 Hive 的 MapReduce/Tez 引擎无法动态扩内存,结果就是 java.lang.OutOfMemoryError: Java heap space 或任务卡死在 99%。
这不是 SQL 写错了,是执行模型决定的——Hive 不像 Spark SQL 那样自动做 map 端预排序或 partial aggregate。
用 pmod(hash(key), N) 打散分组是唯一可行解
不能用 RAND(),它每次计算结果不同,会导致同一 key 的记录被分到不同 reducer,后续无法合并;必须用确定性哈希,确保同一 key 总是落到固定几个 bucket。
-
pmod(hash(user_id), 10)是标准写法,10可根据倾斜程度调大(如 50、100),但不宜过大,否则小 key 也分散,增加 shuffle 量 - 第一阶段加前缀后重算局部序号:
ROW_NUMBER() OVER (PARTITION BY user_id, pmod(hash(user_id), 10) ORDER BY ts) - 第二阶段再按
user_id聚合,取最小序号或累加模拟全局序,取决于是否需要严格分页
什么时候必须补第二阶段排序
如果你要的是「每个用户内按时间严格递增的全局序号」(比如分页查第 10~20 条),只靠打散 + 局部 ROW_NUMBER() 不够——不同 bucket 的局部序号会重复,必须补一层 ORDER BY ts, prefix, rn_local 再重排:
SELECT user_id, ts,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY ts, prefix, rn_local) AS rn_global
FROM (
SELECT user_id, ts,
pmod(hash(user_id), 10) AS prefix,
ROW_NUMBER() OVER (PARTITION BY user_id, pmod(hash(user_id), 10) ORDER BY ts) AS rn_local
FROM events
) t
注意:这里 ts 必须能保证唯一性,否则仍可能因排序键冲突导致序号不准;若 ts 有重复,建议拼上 rowid 或自增字段兜底。
别指望调参数绕过这个问题
hive.tez.container.size 或 mapreduce.reduce.memory.mb 加得再大,也只是把 OOM 推迟,且浪费集群资源。更危险的是误开 hive.groupby.skewindata=true ——它对 GROUP BY 有效,但对窗口函数完全无效,Hive 不会为此改写窗口执行计划。
真正容易被忽略的一点:打散后的 bucket 数不是越多越好。实测中,当 N=100 时,shuffle 数据量可能翻倍,而倾斜缓解收益趋缓。建议先用 SELECT user_id, COUNT(*) FROM t GROUP BY user_id ORDER BY COUNT(*) DESC LIMIT 10 查出 top 倾斜 key,再估算合理 N 值(例如最大 key 占比 15%,则 N ≥ 15 即可)。

















