Skip to content

Hive 数据倾斜怎么识别与处理 ​

难度与前置

难度:进阶
前置:会写 Hive SQL;知道 Map / Reduce 与 Join 基本概念;能看 Job UI。
读完能做什么:区分 Map 端与 Reduce 端倾斜,用 SQL 摸热点,按场景改写 Join/聚合,并用任务时长与结果对齐验收。

适用场景 ​

  • 某个 Reduce 长时间 99%,其余已结束
  • 同样 SQL,某天因头部渠道/商家暴量而超时
  • DISTRIBUTE BY / GROUP BY 后明显长尾

不适用:

  • 全阶段均匀慢(小文件、全表扫、集群队列问题)
  • 引擎已是 Spark SQL:思路可借鉴,参数与 UI 以 Spark 为准(见 Spark Join 倾斜)

真实场景(一条) ​

Hive 日报:GROUP BY channel_id 汇总消耗。Tez 上多数 Reducer 1 分钟结束,1 个 Reducer 跑 40 分钟。摸底发现 Top1 渠道占当日 35% 行数。

改法:对该渠道走单独分支(先过滤热点再 UNION ALL),并对普通渠道保留原聚合;或对 Key 加盐两阶段聚合。墙钟从 45 分钟降到 12 分钟,金额与改前一致。

怎么做(步骤清单) ​

1. 判断倾斜落在哪一端 ​

类型现象常见原因
Map 端倾斜少数 Map 读极大分片输入文件大小不均、压缩块、分区内倾斜文件
Reduce / Join 倾斜少数 Reducer 狂吃 ShuffleGROUP BY / JOIN Key 热点、空值扎堆
动态分区写倾斜写某分区极慢单分区数据过大或小文件爆炸

先看 Job UI 的 Map/Reduce 进度条与字节计数,再决定改输入还是改 Key。

2. 摸底热点 Key ​

sql
SELECT channel_id, cnt,
       cnt * 1.0 / SUM(cnt) OVER () AS ratio
FROM (
  SELECT channel_id, COUNT(1) AS cnt
  FROM dwd_ad_spend_detail_di
  WHERE dt = '${biz_date}'
  GROUP BY channel_id
) t
ORDER BY cnt DESC
LIMIT 30;

同时查空值:

sql
SELECT
  SUM(CASE WHEN channel_id IS NULL OR channel_id = '' THEN 1 ELSE 0 END) AS nullish_cnt,
  COUNT(1) AS total_cnt
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}';

3. 策略选择 ​

场景做法
小表 Join 大表Map Join(/*+ MAPJOIN(small) */ 或自动)
少量热点 Key热点拆分:热点单独算 + 非热点原逻辑,再 UNION ALL
GROUP BY 倾斜两阶段聚合:先按 key, salt 局部聚合,再按 key 最终聚合
空值扎堆空键打散或单独支路
输入文件不均合并小文件 / 调整分片,或先 DISTRIBUTE BY 重分发

4. Map Join(优先尝试) ​

sql
SET hive.auto.convert.join=true;
SET hive.mapjoin.smalltable.filesize=67108864; -- 64MB,按集群调

SELECT /*+ MAPJOIN(d) */
  f.bill_id, f.channel_id, f.cost_amount, d.channel_name
FROM dwd_ad_spend_detail_di f
LEFT JOIN dim_ad_channel_df d
  ON f.channel_id = d.channel_id
 AND d.dt = '${biz_date}'
WHERE f.dt = '${biz_date}';

维表必须去重,否则 Map Join 一样会放大行数。

5. 两阶段聚合(GROUP BY 倾斜) ​

sql
-- 阶段 1:加盐局部聚合
WITH partial AS (
  SELECT
    channel_id,
    CAST(FLOOR(RAND() * 16) AS INT) AS salt,
    SUM(cost_amount) AS cost_amount,
    COUNT(1) AS bill_cnt
  FROM dwd_ad_spend_detail_di
  WHERE dt = '${biz_date}'
  GROUP BY channel_id, CAST(FLOOR(RAND() * 16) AS INT)
)
-- 阶段 2:去盐最终聚合
INSERT OVERWRITE TABLE dws_ad_channel_1d PARTITION (dt='${biz_date}')
SELECT channel_id,
       SUM(cost_amount) AS cost_amount,
       SUM(bill_cnt) AS bill_cnt
FROM partial
GROUP BY channel_id;

6. 热点拆分模板 ​

sql
-- 非热点
SELECT channel_id, SUM(cost_amount) AS cost_amount
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}'
  AND channel_id NOT IN ('hot_channel_1', 'hot_channel_2')
GROUP BY channel_id

UNION ALL

-- 热点:可加盐或提高并行后单独聚合
SELECT channel_id, SUM(cost_amount) AS cost_amount
FROM dwd_ad_spend_detail_di
WHERE dt = '${biz_date}'
  AND channel_id IN ('hot_channel_1', 'hot_channel_2')
GROUP BY channel_id;

热点名单可落成维表,由调度每日刷新,避免 SQL 写死。

7. 参数辅助(不能替代改 SQL) ​

sql
SET hive.optimize.skewjoin=true;
SET hive.skewjoin.key=100000;          -- 超过阈值的 Key 走倾斜优化
SET hive.exec.reducers.bytes.per.reducer=67108864;

倾斜优化依赖引擎实现与统计信息;统计过期时可能误判。生产仍以主动改写为主。

常见坑(至少牢记这些) ​

  1. 只加 Reducer 个数
    同一 Key 仍进同一 Reduce,长尾 Key 依旧。

  2. Map Join 维表有重复
    速度上去了,金额翻倍;先验 channel_id 唯一。

  3. RAND 盐值在两阶段之间不一致用法
    最终聚合忘记去掉 salt,结果按盐值拆行,下游重复计算。

  4. 把「倾斜优化」参数当银弹
    未收集统计 / 版本行为差异,导致计划不变或更慢。

  5. 忽略动态分区与小文件
    写环节倾斜被当成计算倾斜,越加 Reduce 小文件越多。

如何验收(可量化) ​

  • [ ] Job UI:最慢 Reduce 与中位数耗时比下降到约定阈值(如 < 3)
  • [ ] 同日 SUM(cost_amount) / 行数与改前基准一致
  • [ ] 热点 Key 名单或加盐策略有注释/文档
  • [ ] Map Join 场景下维表唯一性检查通过
  • [ ] 连续多日头部渠道暴量时任务仍在 SLA 内
sql
-- 结果对齐
SELECT SUM(cost_amount) AS cost_sum, COUNT(1) AS channel_cnt
FROM dws_ad_channel_1d
WHERE dt = '${biz_date}';
bash
# Tez / MR UI 记录:max_reduce_time, median_reduce_time

相关面试题 ​

占位:与题库小程序对齐后补链接(建议关联:Hive 倾斜表现、MapJoin、两阶段聚合、skewjoin 参数)。


改写说明:结构与判断标准参考 Wood 原创数仓/面试文,已改写成可执行清单;未包含未公开公司数据与内部表名。