Skip to content

动态分区写入坑与验收 ​

难度与前置

难度:进阶
前置:写过 PARTITIONED BY 表;遇到过「只写到默认分区」或小文件炸集群。
读完能做什么:正确打开动态分区、控制并发与文件数,避开空分区/错分区,并用 HDFS + SQL 验收。

适用场景 ​

  • 一次作业按 dt/hour/region 等多级分区写入,分区值来自数据列
  • 补数跨多天:希望一条 SQL 写出多个 dt
  • Spark/Hive 混部,需要统一动态分区纪律

不适用:

  • 单分区已知且固定(静态 PARTITION (dt='...') 更清晰)
  • 极度高基数字段当分区(如 user_id)——应改分桶/不分区
  • 纯 小文件治理 已有专项、本文只谈动态分区交叉点

真实场景(一条) ​

晚间 ODS→DWD:用动态分区一次写 24 个 hour。未设 hive.exec.max.dynamic.partitions,且 reducer=200,产出上万小文件,NameNode RPC 打满;另有一批 hour=NULL 落到 __HIVE_DEFAULT_PARTITION__。修复:限制动态分区数、先过滤空分区键、合并输出(或降低并行后 ALTER 合并),并加验收:禁止默认分区、每分区文件数上限。

怎么做(步骤清单) ​

1. 开关与上限(会话级先写进作业头) ​

sql
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;  -- 多分区列或无静态前缀时常用
SET hive.exec.max.dynamic.partitions=1000;
SET hive.exec.max.dynamic.partitions.pernode=100;
SET hive.error.on.empty.partition=true;         -- 有则打开,防止空分区刷屏
text
strict:至少有一个静态分区列
nonstrict:分区全动态——更灵活也更易失控,必须配 max

2. INSERT 列序:分区列必须在 SELECT 末尾 ​

sql
INSERT OVERWRITE TABLE dwd_evt_di PARTITION (dt, hour)
SELECT
  event_id,
  user_id,
  event_ts,
  date_format(event_ts, 'yyyy-MM-dd') AS dt,
  date_format(event_ts, 'HH')         AS hour
FROM ods_evt_di
WHERE biz_date = '${biz_date}'
  AND event_ts IS NOT NULL;

3. 先清洗分区键,再写入 ​

sql
-- 禁止 NULL/空串进分区
SELECT *
FROM src
WHERE dt IS NOT NULL AND trim(dt) <> ''
  AND hour IS NOT NULL AND hour RLIKE '^[0-9]{2}$';

4. 控制小文件(与动态分区强绑定) ​

手段说明
降低并行SET mapreduce.job.reduces=... / Spark repartition 到目标分区数量级
两阶段写先写临时非分区/单分区,再按分区分发合并
事后合并按分区 INSERT OVERWRITE 自身或 concat 工具
文件格式ORC/Parquet + 合理 stripe/block

参见 Hive 小文件治理。

5. OVERWRITE 范围意识 ​

text
动态分区 OVERWRITE:通常只覆盖「本次出现的分区」,未出现分区保留
不要假设「整表被清空」;补数漏天时旧脏分区可能还在
静态 OVERWRITE PARTITION (dt='x'):语义更直观,跨天补数可循环静态写

6. Spark 写 Hive 动态分区注意点 ​

scala
// 示意
spark.conf.set("spark.sql.sources.partitionOverwriteMode", "dynamic")
df.write.mode("overwrite")
  .format("orc")
  .partitionBy("dt", "hour")
  .saveAsTable("dwd_evt_di")
// 或 insertInto;确认 partitionOverwriteMode=dynamic,避免整表误删

7. 元数据与修复 ​

sql
MSCK REPAIR TABLE dwd_evt_di;           -- 外部表目录手工落盘后
-- 或
ALTER TABLE dwd_evt_di ADD IF NOT EXISTS PARTITION (dt='2026-09-25', hour='10');

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

  1. 忘记开动态分区 / 仍是 strict
    任务失败或写到错误语义;日志里一堆 partition 相关报错被忽略。

  2. 分区列 NULL → __HIVE_DEFAULT_PARTITION__
    报表按 hour 聚合「少一块」,默认分区吃掉脏数据。

  3. 高基数动态分区
    城市×小时×活动 ID 笛卡尔,分区爆炸、元数据打爆。

  4. reducer/task 数远大于分区数
    每分区大量小文件,后续 慢 SQL 与 NN 压力。

  5. 以为动态 OVERWRITE 会删掉未触及分区
    补数后旧错误分区残留,对账「多一天」。

  6. SELECT 列顺序与分区列错位
    数据写进错误分区目录, silently 错位,极难查。

如何验收(可量化) ​

  • [ ] 作业头含 dynamic partition 开关与 max 上限
  • [ ] 目标表无 __HIVE_DEFAULT_PARTITION__
  • [ ] 本次应写分区列表与 SHOW PARTITIONS 交集完整
  • [ ] 每分区文件数 ≤ 阈值(如 50)或合并后达标
  • [ ] 行数 / checksum 与源或上游分区合计一致
sql
-- 1) 禁止默认分区
SHOW PARTITIONS dwd_evt_di PARTITION (dt='${biz_date}');
-- 目检:无 hour=__HIVE_DEFAULT_PARTITION__

SELECT COUNT(*) AS bad
FROM dwd_evt_di
WHERE dt='${biz_date}' AND hour IS NULL;
-- 期望 0(若 hour 非分区列同理巡检)
sql
-- 2) 分区行数验收
SELECT hour, COUNT(*) AS cnt
FROM dwd_evt_di
WHERE dt='${biz_date}'
GROUP BY hour
ORDER BY hour;
-- 与上游按 hour 聚合对比,允许误差=0
bash
# 3) 文件数与默认分区目录
hdfs dfs -ls /warehouse/dwd/evt_di/dt=${biz_date}/ | head
hdfs dfs -count /warehouse/dwd/evt_di/dt=${biz_date}/hour=*
# 期望:无 __HIVE_DEFAULT_PARTITION__;每 hour 文件数可控

# 统计某分区文件数
hdfs dfs -count /warehouse/dwd/evt_di/dt=${biz_date}/hour=10
sql
-- 4) checksum
SELECT COUNT(*) AS cnt, SUM(hash(event_id)) AS h
FROM dwd_evt_di WHERE dt='${biz_date}';

相关面试题 ​

占位:与题库小程序对齐后补链接(建议关联:Hive 动态分区、小文件、INSERT OVERWRITE 语义、默认分区)。


改写说明:结构参考 Wood 原创 Hive 实践,已改写成可执行清单;未包含未公开集群路径。