数仓任务用全量刷新还是增量更新?怎么选、增量怎么保证正确?
考察意图
考察候选人会不会把「全量简单、增量省资源」落成可辩护的选型:先看数据变更形态和可重跑窗口,再谈分区覆盖、水位和回放。只背 MERGE/overwrite 名词,却说不清什么表必须全量、增量漏改怎么发现、迟到数据怎么补,这题就浅了。
回答框架
- 先对变更形态:快照型每天整表替换,还是日志型只来新增/变更;有没有回撤和迟到。
- 全量:读上游完整快照,覆盖目标分区或整表,语义简单、好对账,代价是扫描和写出都大。
- 增量:按时间水位或变更日志只处理新数据,省资源,但必须处理乱序、回撤、漏抽和重跑。
- 正确性三件套:幂等写出(分区覆盖/MERGE)、水位可回放、全量对账兜底。
- 选型口诀:小表/维表/口径常变走全量;大事实表按分区增量,定期全量校准。
参考答案(口述版)
一句话:全量用完整快照覆盖,换资源买正确和简单;增量只处理新变化,省扫描但要把水位、回放和对账设计进去。没有「永远增量」这回事,只有「日常增量、出错能回放、定期全量校准」。
先看数据怎么变,再谈刷新方式。
- 上游每天给你一张完整快照(用户维、配置表、全量导出):目标表本质是「昨天那一版长什么样」。这种表全量覆盖最干净。
- 上游是业务流水或 CDC(订单、点击、账变):事实按事件追加,按 dt 或主键变更加增量。
- 还有第三种:看起来像流水,但源库会改历史(售后改金额、状态回撤)。增量必须能重放那一段,不能只 append。
全量刷新。 读上游当日完整集合,写出覆盖目标。常见落地是分区覆盖:INSERT OVERWRITE TABLE ads_user_daily PARTITION (dt='昨天'),或整表替换后改指针。优点:逻辑等于「昨天快照」,重跑就是再覆盖一次,对账用行数+主键哈希就够。缺点:维表 10 亿、每天只变 0.1%,你仍扫全量、写全量,贵,也容易把高峰窗口打满。
增量更新。 只处理「上次水位之后」的变化。水位可以是业务时间(updated_at > last_watermark)、分区日期(只跑 dt=昨天)、或 CDC 位点(LSN/binlog)。写出必须幂等:
- 日分区事实:覆盖 dt 分区,不要往分区里 append 出重复文件。
- 主键型明细:按主键 MERGE(有则更新、无则插入),回撤用删除标记或版本比较。
口述级增量骨架:
-- 日分区事实:只跑昨天,覆盖写出,重跑不双计
INSERT OVERWRITE TABLE dwd_order_item PARTITION (dt='${bizdate}')
SELECT order_id, user_id, sku_id, pay_amt, pay_time, ...
FROM ods_order_item
WHERE dt = '${bizdate}';
-- 主键维:按更新时间拉增量,MERGE 进当前像
MERGE INTO dim_user t
USING (
SELECT * FROM ods_user_cdc
WHERE updated_at > '${last_watermark}'
) s
ON t.user_id = s.user_id
WHEN MATCHED AND s.updated_at >= t.updated_at THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
增量最容易错的三件事。
1. 水位切错:用处理时间当业务时间,源库卡住时你会以为「没新数据」;或 > 写成 >= 在边界重复、写成 > 又漏掉同一秒多条。约定半开半闭:(last_wm, current_wm],并持久化水位表。
2. 迟到和回撤:T-2 的订单今天才进 ODS,只跑昨天分区会漏。增量窗口要留 lookback(例如重跑近 3 天分区),或按事件日 MERGE 进历史分区。回撤要版本字段,不能只 insert。
3. 漏抽没有报警:增量成功但少了 10% 变更,调度是绿的。必须有全量对账:主键差集、金额 checksum,按周或按天对热点表。
怎么选。
- 小表、维表、枚举、口径常改的指标表:全量。正确性优先,省下来的钱不够一次事故。
- 大事实、按日不可变事件(点击、曝光):按 dt 增量覆盖。
- 会改历史的大表:增量 MERGE + 定期全量校准(周末或月末拿源库快照对一遍)。
- 口径重刷、回填 30 天:走分区覆盖回放,不要手写 30 条 UPDATE。
收尾三句:按变更形态选型、增量必须幂等且可回放、全量对账兜底。能主动说「日常增量、出错回放近 N 天、定期全量校准」,这题就过了。
关键词
面试官可能的追问
追问 1:一张每天只变 1% 的 10 亿行用户维,为什么很多人仍坚持全量?增量怎么才能稳?
全量的理由是语义简单:当天快照 = 上游当天导出,对账一行哈希就能做,也不怕漏掉「没进 CDC」的冷更新。10 亿行全量贵,但维表事故比多扫一次更贵。要做增量必须同时具备:可靠 CDC 或 updated_at 被所有更新路径维护、MERGE 按版本去重、以及至少日级全量对账(抽样主键 + 关键属性 checksum)。没有对账的增量,等于默许静默漂移。实践上常见「日增量 + 周全量校准」。
追问 2:只覆盖昨天分区的增量任务,T-3 的迟到订单怎么补?
事件属于 T-3,只跑昨天分区永远写不进去。做法有三:一是任务每次重跑近 N 天分区(lookback),用事件日覆盖历史分区;二是迟到通道单独 MERGE 进对应 dt;三是离线日终用源库/ODS 按事件日再校准一次。Watermark 只保证「晚于 N 天的不再进日常增量」,不保证那些天的数已经对。回补窗口要写进 SLA,并禁止在不完整的历史分区上对外报累计 GMV。
追问 3:全量和增量混跑,会不会把数据写花?
会,如果两套作业写同一目标、互不知道水位。规则:同一张表同一分区同一时刻只允许一种写出者。混跑的正确姿势是「增量日常写、全量校准用同一套覆盖语义」——全量也是 INSERT OVERWRITE 分区或整表 swap,而不是在增量文件旁边再 append 一份。全量校准前先停增量或锁分区,校准后把水位重置到校准快照对应的时间,否则下一次增量会按旧水位重放,把校准结果再打乱。
常见踩坑
- 把增量理解成「只 insert 不覆盖」,重跑或迟到数据把分区写成双份
- 用水位
updated_at > now()-1d这种滑动窗口,任务延迟时漏抽或重复抽且无法回放 - 源库改历史却按「只追加事实」建模,金额回撤永远进不了数仓
- 增量任务绿了就对外报数,没有全量对账,静默漏数要到业务对账才爆
记忆锚点
日常增量、出错回放、定期全量校准;增量省的是扫描,不是正确性,写出必须覆盖或按主键 MERGE。