订阅更新

分区字段选错了会怎样?——一次错误改写要动几百 TB

数仓开发手册 第 10 题 约 9 分钟读完

本文目录
  1. 先说结论
  2. 业务场景:某出海硬件品牌 10 亿行订单表的分区选择
  3. 段落一:分区的本质——目录隔离
  4. 段落二:选分区字段的 4 条标准
  5. 段落三:按『业务时间』vs『处理时间』的经典选择
  6. 段落四:二级分区——什么时候要、什么时候不要
  7. 段落五:分区粒度——按天还是按小时
  8. 面试高频追问
  9. 实战踩坑:按 status 分区的翻车故事
  10. 总结

先说结论

分区的目的只有两个:(1)让查询能裁剪(PartitionFilter 只扫相关分区);(2)让写入能并行(不同分区写不同目录)。两者兼顾选字段。

90% 场景按日期分区是对的,但要分清『业务日期』和『处理日期』——跨时区、补数、延迟到达这几个场景两者不同。

分区粒度要匹配查询粒度。大多数查询按天查就按天分区、按小时查就按小时分区。过细(按分钟)分区数炸、过粗(按月)裁剪无效。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

分区设计是数仓建模题的最后一道常问题。题目看起来简单——『一张 10 亿行的用户订单表,按什么分区?』——很多人不假思索答『按日期』。但真实项目里,分区选错的代价极其大:写入热点、查询性能不均、改回来要重刷几百 TB 数据。这题考的是你有没有因为分区选错而痛过。

业务场景:某出海硬件品牌 10 亿行订单表的分区选择

我在某出海硬件品牌有张订单事实表日均 800 万行、累计 10 亿+。分区选型当时有三个候选:(1)按 order_date 分区;(2)按 dt(处理日期)分区;(3)按 region + dt 二级分区。最后选了第三个,原因下面详细讲。

-- 候选方案一:按 order_date
CREATE TABLE fact_order_v1 (...)
PARTITIONED BY (order_date DATE);
-- 问题:跨时区订单落到哪个分区?
-- 美国用户 3/1 下单(美东时间),按 UTC 是 3/2
-- 补数修正历史数据要重刷分区
 
-- 候选方案二:按 dt(处理日期)
CREATE TABLE fact_order_v2 (...)
PARTITIONED BY (dt STRING);
-- 问题:所有订单都落到『今天』分区,历史数据重新灌需要改分区
 
-- 最终方案:按 region + dt
CREATE TABLE fact_order (
  order_id BIGINT, uid BIGINT, amount DECIMAL, ...
) PARTITIONED BY (region STRING, dt STRING);
-- region=US 的数据都落 US 目录,dt 按用户本地日
-- 优点:裁剪按地区 + 按日期,绝大多数分析都能裁

段落一:分区的本质——目录隔离

分区在 Hive/Spark/Iceberg 里本质都是目录或 manifest 隔离。分区字段 = 目录名。查询时的 WHERE 过滤能推到分区层、直接跳过不相关目录——这就是『分区裁剪』。

Plain

按 dt 分区的物理存储:
 
/warehouse/fact_order/
  dt=2024-03-13/
    part-00001.parquet
    part-00002.parquet
    ...
  dt=2024-03-14/
    part-00001.parquet
    ...
  dt=2024-03-15/   ← WHERE dt='2024-03-15' 只读这一个目录
    ...
 
如果 WHERE 里没写 dt,就是全表扫 = 扫完所有目录。
所以分区表没写分区过滤 = 没分区。

段落二:选分区字段的 4 条标准

标准理由反例
被 WHERE 频繁用到不会被 WHERE 用的字段分区毫无意义按 user_id 分区(查询几乎不会 WHERE user_id 过滤)
基数适中(100-10000)太低裁不掉,太高分区爆炸按 status(基数 < 10)或按 uid(基数 > 千万)
分布相对均匀倾斜分区照样慢按 country(中国数据占 80%)
业务稳定不变分区字段变值 = 整行要挪位按用户当前 VIP 等级分区

段落三:按『业务时间』vs『处理时间』的经典选择

大部分日志/事件表都有两个时间戳:event_time(事件发生时间)和 process_time(ETL 处理时间)。按哪个分区?

按 process_time(dt)的优势:写入只改最新分区、ETL 幂等;劣势:跨时区时同一用户的同一天数据会被拆到两个分区。

按 event_time 的优势:业务查询最自然(按事件发生日查);劣势:数据迟到会回写历史分区,ETL 幂等难做。

我的实战经验:小厂用 dt(简单稳定),大厂出海场景按业务时间(跨时区必须)。我在某短剧出海公司就是按 『用户本地 event_date』分区的——相当于按用户时区切日。这个设计是踩了 dt 分区的坑之后改的。

段落四:二级分区——什么时候要、什么时候不要

二级分区(比如 region + dt)能进一步减小扫描范围,但不是所有场景都要用。

Plain

二级分区适合的场景:
  ✅ 查询经常按两个字段联合过滤
  ✅ 第一个字段基数 10-100(比如地区、业务线)
  ✅ 数据量大(> 10 亿行),裁剪价值显著
 
二级分区不适合的场景:
  ❌ 第一级字段基数 > 1000,分区数爆炸
  ❌ 查询只按一个字段过滤,另一个没用
  ❌ 小表(< 1 亿行),裁剪价值小于管理成本
 
分区数上限(经验值):
  Hive:单表 10 万分区以内
  Spark / Delta:同上
  超过就要考虑桶表(bucket)或调整分区粒度

段落五:分区粒度——按天还是按小时

实时数仓和 IoT 场景常见按小时分区。选按天还是按小时的标准:

粒度适合场景风险
按月冷历史表、合规归档分区太粗、裁剪弱
按天绝大多数离线数仓最平衡的默认选项
按小时实时看板、IoT 高频事件分区数量膨胀,24×天数
按分钟超高频、短保留期分区数极大,管理成本高

经验值:一张表的分区总数控制在 1-10 万,超过 10 万要考虑合并。按小时分区留一年就是 8760 个分区,已经不小了。

面试高频追问

追问一:如果当初选错了分区字段,怎么改?

这是最痛的问题。改分区字段 = 重建表 + 重写所有数据 + 下游 SQL 全改。具体步骤:(1)新建一张带正确分区字段的表;(2)从原表 INSERT OVERWRITE 到新表(按新分区字段重新组织数据);(3)下游 SQL 批量改过来;(4)切流量 / 灰度;(5)下线老表。我在某出海硬件品牌改过一次,改的是从 dt 改成 region+dt,10 亿行数据重刷了 8 小时,下游 40 个任务逐个改。教训是:分区字段第一次就要选对,返工成本太大。

追问二:分区字段和桶字段的区别是什么?

分区是目录隔离、主要服务查询裁剪;桶是文件内哈希散列、主要服务 Join 和采样。一般分区字段选『查询 WHERE 常用』(比如 dt),桶字段选『Join Key』(比如 uid)。桶表 Join 能避免 Shuffle——两边相同 uid 进同样的桶,Map-side Join 直接做。但实务里维护成本高,大多数项目只用分区,桶表用得不多。

追问三:Iceberg / Hudi 这种数据湖格式的分区和 Hive 有什么不一样?

数据湖格式引入了『隐藏分区』和『分区演化』两个大特性。隐藏分区:你只 WHERE event_time,Iceberg 自动根据分区规范 event_time.day 做裁剪,不用 SQL 里写 dt=。分区演化:可以在线修改分区规则(比如从 day 改成 hour),旧数据不用重写,新数据按新规则。这个能力正好解决了上一题的痛点——改分区字段不用全表重刷了。

实战踩坑:按 status 分区的翻车故事

我见过一个同事设计过『按 order_status 分区』的订单表。初衷是『status 是 WHERE 常用字段』。但 order_status 只有 5 种值(待付款/已付款/已发货/已完成/已取消),基数太低,分区裁剪基本没意义;更糟的是 status 会变——订单从『待付款』变『已付款』,对应记录要从一个分区挪到另一个分区,HDFS 里的分区表做不到『update 字段』,只能重写。

这个表上线一周就翻车了,改回按 dt 分区重写了全表。教训:分区字段的『值稳定』比『查询常用』更重要。变值的字段坚决不能做分区字段。

Plain

分区字段选型红线(我的硬规则):
 
1. 字段值会变 → ❌ 禁止(order_status / vip_level)
2. 字段基数 < 10 → ❌ 禁止(gender / is_active)
3. 字段基数 > 100万 → ❌ 禁止(uid / order_id)
4. 字段分布极度倾斜 → ⚠️ 警告(country 且 70%+ 一个国家)
5. 字段不会被 WHERE 用 → ❌ 禁止(created_by / source_ip)

总结

分区题的高分答案是能讲出三件事:(1)选分区字段的 4 条标准;(2)业务时间 vs 处理时间的选择(尤其出海场景);(3)一个真实的返工故事。分区看起来简单,实则是建模里最容易犯方向性错误的地方——选错了改回来的成本远超第一次多想半天。

下一篇:『新表上线数据错了怎么查?』——进入第四阶段『生产问题』。面试的难度曲线到这里开始上升,考察的是真实故障处置能力。

你们表的分区字段有没有出现过『选错了但懒得改』的遗产?还是已经经历过血的重刷?

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

数仓开发 面试通关手册

第11-15篇:生产问题处置 + 架构起点(面试 35-50 分钟)

Wood聊大数据

9年大数据老兵,聊技术、聊职场、聊成长

按面试官真实提问顺序设计的 20 道题

第三部分导读(生产问题处置 + 架构起点)

面试进行到 35 分钟之后,话题会从『你会做什么』拐到『你扛过什么』。这本册的第 11-14 篇就是典型的生产问题处置四连问——新表数据错了怎么查、T+1 变 T+2 怎么办、上游 schema 悄悄变了怎么防、1200 行 SQL 怎么 review。这四题能考出一个候选人是不是真的在线上待过。

第 15 篇是架构起点——同指标多部门不一致。这题既有生产问题的痕迹(故障驱动),又有架构视野(需要跨团队、跨系统的解决方案)。答好这题相当于给自己从『执行型』往『架构型』转身一个台阶。

这五题里几乎每一题都有我踩过的具体故事:汇率含义变导致礼物总额暴涨 300%、跨时区切日让美国数据延迟一天、1200 行 SQL 重构到 80 行、CEO 问『DAU 到底信哪个』。面试时能讲出具体故事就能压住节奏。

— Wood,2026 年春

刷题进度保存在当前浏览器