首页/数据/数据工程与 ETL 速查/幂等落库:主键 upsert
CDC 与同步53 条

幂等落库:主键 upsert

增量同步最常见的幂等写法是主键 upsert:MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,PostgreSQL 用 INSERT ... ON CONFLICT,Hive/Spark/Iceberg/Delta 用 MERGE。同一主键重复消费只会覆盖成相同结果,天然容忍重放;delete 事件要另行按主键删除或标记。

幂等落库的标准做法

增量同步最常见的幂等写法是主键 upsert:MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,PostgreSQL 用 INSERT ... ON CONFLICT,Hive/Spark/Iceberg/Delta 用 MERGE。同一主键被重复消费只会覆盖成相同结果,天然容忍重放,这也是 CDC 链路能「重跑不炸」的基础。删除事件需要另行处理:按主键删除或打墓碑标记,不能只靠 upsert。

基本信息

前提条件目标表必须有主键或唯一索引,否则 upsert 退化为重复插入
旧写法VALUES(col) 引用方式自 MySQL 8.0.20 起被弃用
MERGEHive 3 / Spark / Iceberg / Delta 的通用替代写法
删除处理op=d 需要 DELETE 或墓碑标记,不能只 upsert

典型使用场景

  • CDC 增量落库:按主键覆盖写入,容忍重复消费
  • 补数重跑:同一批数据重放后结果一致
  • 多源汇聚:不同源头写同一张表时按主键合并
  • 湖表更新:Iceberg/Delta 用 MERGE 实现行级更新
  • 删除同步:把删除事件转换为按主键删除或标记

MySQL 行别名写法

MySQL 行别名写法
INSERT INTO dw.orders (order_id, amount, status, updated_at)
VALUES (?, ?, ?, ?) AS new          -- 8.0.19+ 支持行别名,替代已弃用的 VALUES(col)
ON DUPLICATE KEY UPDATE
  amount = new.amount,
  status = new.status,
  updated_at = new.updated_at;

幂等手段对照

数据库与引擎写法注意点
MySQLON DUPLICATE KEY UPDATE需唯一索引,8.0.19+ 用行别名
PostgreSQLON CONFLICT ... DO UPDATE冲突列需有唯一约束
Hive 3MERGE INTO需要事务表(如 ORC + ACID)
Iceberg / DeltaMERGE INTO支持行级更新,注意小文件问题
消息侧去重按主键做窗口去重有窗口限制,不是永久幂等
Hive 的 MERGE 需要表具备事务特性(例如 ORC 加 ACID),使用前先确认表属性,否则语句会直接报错。
删除事件在部分下游表现为 op=d 或墓碑记录:如果链路只处理 insert 与 update,源端删掉的行会永远留在目标端,对账时行数只多不少,很难第一时间被注意。

顺序与批次的两点注意

upsert 解决了「重复写入结果一致」,但没有解决「顺序」:同一主键的多次变更如果乱序到达,最终留下的是最后写入的那一版,而不是最新的那一版。CDC 场景下这一点靠 binlog 顺序天然保证,一旦中间环节并行处理就可能被打乱。另外,大批量 MERGE 或批量 upsert 要控制单次批次规模,批次过大既拉长锁等待,也让失败重试的代价成倍上升,通常按几千行一批、配合限流比较稳妥。

易错点

  • 目标表没有主键或唯一约束,upsert 变成重复插入,行数翻倍
  • 只处理 insert 与 update,源端删除的行永远留在目标端
  • 批量 upsert 时不限制批次大小,大事务把目标库锁等待拉长
  • 把去重放在应用内存里,进程重启后失效,重复数据照样落库

数据来源与参考

  • MySQL 8.0 Reference Manual: INSERT ... ON DUPLICATE KEY UPDATE
  • Apache Hive 官方文档(MERGE)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌