幂等落库的标准做法
增量同步最常见的幂等写法是主键 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 起被弃用 |
| MERGE | Hive 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;幂等手段对照
| 数据库与引擎 | 写法 | 注意点 |
|---|---|---|
| MySQL | ON DUPLICATE KEY UPDATE | 需唯一索引,8.0.19+ 用行别名 |
| PostgreSQL | ON CONFLICT ... DO UPDATE | 冲突列需有唯一约束 |
| Hive 3 | MERGE INTO | 需要事务表(如 ORC + ACID) |
| Iceberg / Delta | MERGE INTO | 支持行级更新,注意小文件问题 |
| 消息侧去重 | 按主键做窗口去重 | 有窗口限制,不是永久幂等 |
Hive 的 MERGE 需要表具备事务特性(例如 ORC 加 ACID),使用前先确认表属性,否则语句会直接报错。
删除事件在部分下游表现为 op=d 或墓碑记录:如果链路只处理 insert 与 update,源端删掉的行会永远留在目标端,对账时行数只多不少,很难第一时间被注意。
顺序与批次的两点注意
upsert 解决了「重复写入结果一致」,但没有解决「顺序」:同一主键的多次变更如果乱序到达,最终留下的是最后写入的那一版,而不是最新的那一版。CDC 场景下这一点靠 binlog 顺序天然保证,一旦中间环节并行处理就可能被打乱。另外,大批量 MERGE 或批量 upsert 要控制单次批次规模,批次过大既拉长锁等待,也让失败重试的代价成倍上升,通常按几千行一批、配合限流比较稳妥。