删除语义怎么同步
同步删除有两种口径:物理删除是目标端按主键真正 DELETE,与源端保持一致;逻辑删除是保留行并置 is_deleted 标记,便于审计与恢复。麻烦点在于源端如果本身就是逻辑删除(执行 UPDATE is_deleted=1),CDC 工具看到的是 update 事件而不是 delete 事件,加工层不做转换的话,目标端会把这条记录当成普通更新继续保留,已经删掉的数据一直挂在下游。所以删除语义必须在建模时显式定义,并在 CDC 接入参数里核对(例如 Debezium 的 skip.messages.without.change 与 tombstones 相关配置)。
基本信息
| 两种口径 | 物理删除(真删)与逻辑删除(标记位) |
|---|---|
| CDC 事件 | op=d 为删除,op=u 且 is_deleted=1 属逻辑删除 |
| 转换位置 | 源表抽取层或加工层显式转换为删除语义 |
| 相关配置 | Debezium 的 tombstone 与消息投递配置按版本核对 |
| 对账口径 | 墓碑表核对时要排除 is_deleted=1 的行 |
典型使用场景
- 下游明细表要求与源端行数一致,需要按主键物理删除
- 需要审计留痕的场景:保留墓碑行并记录删除时间与操作来源
- 用户注销或数据主体删除请求,必须确保下游各层都同步删除
- 拉链表加工时需要识别删除事件,把当前有效记录的结束时间封闭
- 对账任务要区分现存行与已删除行,避免长期差异被误判为质量问题
事件类型与处理
| 源端行为 | 事件类型 | 典型处理 | 注意点 |
|---|---|---|---|
| 物理 DELETE | op=d,before 带旧值 | 目标端 DELETE 或写墓碑行 | 需要主键与旧值可用 |
| 逻辑删除 UPDATE | op=u,after.is_deleted=1 | 加工层转换为删除语义 | 不转换则数据永挂下游 |
| 需要审计留痕 | op=d | 保留墓碑行加删除时间字段 | 查询与对账需过滤 |
| 误删恢复 | op=d 后重新 INSERT | 按主键覆盖或复活墓碑行 | 注意主键复用导致重复 |
墓碑行与过滤写法
墓碑行与过滤写法
-- 删除时写墓碑行,保留审计信息
UPDATE dw.orders
SET is_deleted = 1, deleted_at = NOW()
WHERE order_id = ?;
-- 加工层把逻辑删除的 UPDATE 转成删除语义(Flink SQL 思路)
INSERT INTO dw_orders
SELECT order_id, amount, CAST(NULL AS STRING) AS status, 1 AS is_deleted
FROM ods_orders_cdc WHERE op = 'u' AND is_deleted = 1;
-- 对账与查询都要排除已删除行
SELECT COUNT(1) FROM dw.orders WHERE is_deleted = 0;对账口径要跟着删除语义走:物理删除的表按现存行核对,墓碑表要排除 is_deleted=1 的行,否则行数永远对不上。
把逻辑删除当普通更新处理是最常见的接入错误:表面上任务全绿,下游却保留了大量已删除记录,等业务方发现时已经污染了历史报表。
墓碑表要区分“未删除”和“未知”:源端字段为空时不要默认写成 0,否则会把状态未知的记录当成有效数据参与统计。