首页/数据/数据工程与 ETL 速查/删除语义:物理删除与逻辑删除同步
CDC 与同步53 条

删除语义:物理删除与逻辑删除同步

同步删除有两种口径:物理删除(目标端按主键 DELETE,与源端一致)与逻辑删除(保留行、置 is_deleted 标记)。若源端本身是 UPDATE is_deleted=1 式逻辑删除,CDC 看到的是 update 而非 delete,需要在加工层显式转换为删除语义,否则目标端会当成普通更新保留。

删除语义怎么同步

同步删除有两种口径:物理删除是目标端按主键真正 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 的行

典型使用场景

  • 下游明细表要求与源端行数一致,需要按主键物理删除
  • 需要审计留痕的场景:保留墓碑行并记录删除时间与操作来源
  • 用户注销或数据主体删除请求,必须确保下游各层都同步删除
  • 拉链表加工时需要识别删除事件,把当前有效记录的结束时间封闭
  • 对账任务要区分现存行与已删除行,避免长期差异被误判为质量问题

事件类型与处理

源端行为事件类型典型处理注意点
物理 DELETEop=d,before 带旧值目标端 DELETE 或写墓碑行需要主键与旧值可用
逻辑删除 UPDATEop=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,否则会把状态未知的记录当成有效数据参与统计。

易错点

  • 把源端的 UPDATE is_deleted=1 当普通更新处理,已删数据长期留存在下游,报表口径被污染
  • 对账不剔除已删除行,行数永远差一截,久而久之团队不再信任对账结果
  • 删除后重新写入同主键记录(主键复用),墓碑行与新增行混在一起,无法判断是否为同一业务事件
  • 物理删除且无审计留痕,误删后无法恢复,也无法追溯是哪次任务删掉了数据

数据来源与参考

  • Debezium 官方文档(delete 事件结构)
  • 行业数仓墓碑行与拉链表实践
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌