Hive 动态分区一次写了上千个分区就报错

举报
luckyz 发表于 2026/11/11 09:13:03 2026/11/11
【摘要】 某制造企业数仓里有一张设备运行记录明细表,按天分区,每天凌晨跑增量写入一直很稳。上周业务方要求回补过去一个月的历史数据,开发同学把日期范围一放宽,改成一次 INSERT 写 30 天的分区,结果作业跑了十几分钟后直接失败,日志里只有一行看着很陌生的报错:org.apache.hadoop.hive.ql.metadata.HiveFatalException: [Error 20004]:F...

某制造企业数仓里有一张设备运行记录明细表,按天分区,每天凌晨跑增量写入一直很稳。上周业务方要求回补过去一个月的历史数据,开发同学把日期范围一放宽,改成一次 INSERT 写 30 天的分区,结果作业跑了十几分钟后直接失败,日志里只有一行看着很陌生的报错:

org.apache.hadoop.hive.ql.metadata.HiveFatalException: [Error 20004]:
Fatal error occurred when node tried to create too many dynamic partitions.
The maximum number of dynamic partitions is controlled by
hive.exec.max.dynamic.partitions and hive.exec.max.dynamic.partitions.pernode.
Maximum was set to: 100

SQL 一个字没改,昨天还好好的,今天只是多写了几天数据就炸了。问题不在 SQL,在 Hive 动态分区的两个默认上限上。

一、报错拆开看:撞的是哪一道上限

Hive 给动态分区设了两道上限,默认值都写在官方文档里:

  • hive.exec.max.dynamic.partitions.pernode,默认值 100:单个 mapper 或 reducer 最多创建的动态分区数。
  • hive.exec.max.dynamic.partitions,默认值 1000:一次 DML 总共最多创建的动态分区数。

两道上限的触发时机不一样。单个节点上创建的分区数超过 100,节点直接抛致命错误,整个作业被杀掉,报错里 Maximum was set to: 100 指的就是这一道;如果每个节点都没超 100,但所有节点加起来超过 1000,作业会在最后移动数据之前抛异常。上面那次回补,30 天 × 多条产线的组合早就超过了单节点的 100,所以先撞上的是第一道。

还有一个容易忽略的默认值:hive.exec.dynamic.partition.mode 默认是 strict,严格模式要求 INSERT 时至少指定一个静态分区列。写全动态分区之前,必须先把它设成 nonstrict,否则连跑都跑不起来。

二、为什么偏偏是这张表撞上

动态分区插入时,mapper 拿到的是随机分片的数据。一行数据属于哪个分区,要到写文件时才知道,一个 mapper 手里可能攥着几十个不同分区的数据。分区字段的基数越高,单个 mapper 撞上 100 上限的概率越大。

这张明细表按 dt 分区,一天一个分区,回补 30 天本来只有 30 个分区,怎么会超 100?因为这张表其实是两级分区:PARTITION(dt, line_code),日期乘上十几条产线,30 天下来就是三四百个分区组合,分到同一个 mapper 上的不同组合轻轻松松超过 100。单日增量写入时只有十几个组合,从来碰不到上限,所以一直没暴露。

三、修法一:先把上限调到够用

确认分区设计本身没问题、只是批量回补这一次分区数多,那就直接调大两个上限。注意两个要一起调,只调总量不调单节点,照样会在 100 这里被拦住:

SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
SET hive.exec.max.dynamic.partitions=5000;
SET hive.exec.max.dynamic.partitions.pernode=500;

INSERT OVERWRITE TABLE dwd_device_runtime PARTITION(dt, line_code)
SELECT device_id, run_minutes, energy_kwh, dt, line_code
FROM ods_device_runtime
WHERE dt BETWEEN '2026-10-12' AND '2026-11-10';

调多大合适没有标准答案,按实际分区组合数留两三倍余量即可。这次回补大约 400 个组合,总量设 5000、单节点设 500 绰绰有余。调完之后作业一次跑通。

四、修法二:让同一个分区的数据落到同一个 reducer

只调参数治标不治本。更彻底的做法是在 SQL 里加一句 DISTRIBUTE BY,按分区字段把数据先分发到同一个 reducer 再写,这样每个 reducer 只负责少数几个分区,从根上降低单节点创建的分区数:

SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;

INSERT OVERWRITE TABLE dwd_device_runtime PARTITION(dt, line_code)
SELECT device_id, run_minutes, energy_kwh, dt, line_code
FROM ods_device_runtime
WHERE dt BETWEEN '2026-10-12' AND '2026-11-10'
DISTRIBUTE BY dt, line_code;

这是 Hive 官方教程里推荐的做法:mapper 随机拿数据时不同分区的行混在一起,分区数很容易超;先按分区字段分发,写分区时每个节点手里的分区数就大幅下降。日常的增量作业也可以顺手加上,基本没有额外成本。

五、别只调上限:分区字段选错了,调多大都没用

如果撞上限不是因为回补,而是日常作业天天报,那就别再调参数了,先检查分区字段本身。用设备编号、订单号这种高基数字段做分区字段,一天就能产生几万个分区,上限调到 10 万也撑不住,还会拖垮元数据库和 NameNode。

分区字段应该选低基数、查询时常用来过滤的字段,比如日期、产线、车间。真需要按设备维度分析时,把设备编号放进表里做普通列,查询时用 WHERE 过滤,而不是把它变成一级分区。

六、顺带防一手:小文件还有第三道上限

动态分区写多了,经常顺带踩到第三个默认值:hive.exec.max.created.files,默认值 100000,一次作业所有 mapper 和 reducer 创建的文件总数上限。分区数一多,每个分区再写几个小文件,很容易一起撞线。回补跑完之后可以顺手查一下目标分区的文件数,单个分区几十个小文件就该考虑在写入后做一次合并,或者在 SQL 里控制好每个分区的数据量,别让动态分区一次写出满天小文件。

小结

Hive 动态分区报错 [Error 20004],先别急着改 SQL,对照两个默认值查:单节点上限 hive.exec.max.dynamic.partitions.pernode 默认 100,总量上限 hive.exec.max.dynamic.partitions 默认 1000。一次回补分区数变多,先把两个上限一起调大;想长期稳妥,就在 SQL 里加 DISTRIBUTE BY 分区字段,让同一个分区的数据落到同一个 reducer;如果日常作业也天天撞上限,问题多半出在分区字段选了高基数字段,改分区设计比调参数管用。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。