关闭旧版本的条件
关闭旧版本行的条件必须同时锁定「自然键」与「当前版本」两项:只按自然键更新,会把该实体的全部历史行的 end_date 与当前标志一起改写,版本区间整体损毁;漏掉当前标志这个条件,事后几乎无法从数据看出问题。正确写法是 WHERE 自然键 IN 变更集合 AND is_current = 1,且只更新 end_date 与当前标志两列,执行顺序为先关闭再插入。
基本信息
| 锁定列 | 自然键 + is_current = 1 |
|---|---|
| 可更新列 | end_date 与当前标志,其他列一律不动 |
| 执行顺序 | 先关闭旧行,后插入新行 |
| 幂等保护 | 重跑时按批次判断,避免 end_date 被后一批覆盖 |
| 引擎差异 | 列式引擎大范围 UPDATE 代价高,常见做法是重写受影响分区 |
典型使用场景
- SCD2 增量作业:每个批次都要关闭变更成员的上一个版本
- 失败重跑:作业失败后重跑,关闭逻辑必须幂等,不能把 end_date 改乱
- 存量修复:发现历史区间被误改,按变更明细重新计算 end_date
- 迁移改造:从全量重算迁移到增量关闭,需保证区间结果一致
- 分区重写优化:在 Hive 或 Spark 上改用重写受影响分区替代 UPDATE
- 区间校验:关闭完成后立即校验每个自然键的区间连续且不重叠
关闭旧版本与校验
关闭旧版本与校验
-- 正确:自然键 + 当前标志,缺一不可
UPDATE dim_customer
SET end_date = :batch_dt, -- 关闭日,通常取批次日期减一天
is_current = 0
WHERE customer_id IN (SELECT customer_id FROM changed_keys)
AND is_current = 1;
-- 幂等保护:只关闭还没被处理的当前行
-- AND NOT EXISTS (SELECT 1 FROM dim_customer_hist_audit a
-- WHERE a.customer_id = dim_customer.customer_id
-- AND a.batch_id = :batch_id)
-- 关闭后校验:同一自然键是否仍有零条或多条当前行
SELECT customer_id, sum(case when is_current = 1 then 1 else 0 end) AS cur_cnt
FROM dim_customer GROUP BY customer_id HAVING cur_cnt <> 1;写法对照
| 写法 | 后果 |
|---|---|
| WHERE 自然键 IN (变更集) | 全历史行被一起关闭,区间信息整体损毁 |
| WHERE 自然键 IN (变更集) AND is_current = 1 | 正确,只影响当前版本行 |
| 只按最大版本号关闭 | 变更集与版本号不一致时漏关,出现两条当前行 |
| 关闭与插入不分批次 | 中途失败留下零条当前行,报表取不到属性 |
| 重跑不判批次 | end_date 被重复覆盖,区间漂移 |
在 Hive、Spark 这类以文件重写为代价的引擎上,大规模 UPDATE 会重写整张表或整分区,代价远超预期。常见替代方案是:识别受影响自然键所在分区,重写这些分区,再用原子替换落库;评估时要连着下游读取一起排期。