外观
动态分区写入坑与验收
难度与前置
难度:进阶
前置:写过 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:分区全动态——更灵活也更易失控,必须配 max2. 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');常见坑(至少牢记这些)
忘记开动态分区 / 仍是 strict
任务失败或写到错误语义;日志里一堆 partition 相关报错被忽略。分区列 NULL →
__HIVE_DEFAULT_PARTITION__
报表按 hour 聚合「少一块」,默认分区吃掉脏数据。高基数动态分区
城市×小时×活动 ID 笛卡尔,分区爆炸、元数据打爆。reducer/task 数远大于分区数
每分区大量小文件,后续 慢 SQL 与 NN 压力。以为动态 OVERWRITE 会删掉未触及分区
补数后旧错误分区残留,对账「多一天」。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 聚合对比,允许误差=0bash
# 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=10sql
-- 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 实践,已改写成可执行清单;未包含未公开集群路径。