由 John Doe 十月 23, 2025
你知道如何处理 PostgreSQL 逻辑复制应用变更时的冲突问题吗?

特性提交日志
记录逻辑复制中应用变更时的冲突。
本补丁在应用变更的以下冲突场景中,提供了额外的日志信息:
- insert_exists:插入的行违反了“不可延迟(NOT DEFERRABLE)”的唯一约束。
- update_differ:待更新的行此前已被其他来源(如本地业务、另一发布端)修改。
- update_exists:行更新后的值违反了“不可延迟(NOT DEFERRABLE)”的唯一约束。
- update_missing:待更新的元组(行数据)不存在。
- delete_differ:待删除的行此前已被其他来源修改。
- delete_missing:待删除的元组(行数据)不存在。
对于insert_exists和update_exists冲突,若启用了track_commit_timestamp配置,日志中可包含冲突键的来源(修改源)及提交时间戳详情。
仅当订阅端启用track_commit_timestamp配置时,才能检测到update_differ和delete_differ冲突。
对于排除约束(exclusion constraint)冲突,我们暂不提供额外的日志信息,因为此类约束可定义的规则远比简单的等值校验复杂,解决这类冲突的过程也并非一目了然。若后续有需求,该领域可进一步优化增强。
讨论:https://postgr.es/m/OS0PR01MB5716352552DFADB8E9AD1D8994C92@OS0PR01MB5716.jpnprd01.prod.outlook.com
示例
例如,有个订单表(order,主键order_id)从主库(发布端)同步到备用库(订阅端),同步突然中断,查看订阅端日志发现insert_exists冲突。
查看冲突日志:
ERROR: conflict detected on relation "public.order": conflict=insert_exists
DETAIL: Key already exists in unique index "order_pkey", which was modified locally in transaction 740 at 2025-08-20 15:20:30.127+08.
Key (order_id)=(10086); existing local tuple (10086, '2025-08-20', 'paid'); remote tuple (10086, '2025-08-20', 'unpaid').
CONTEXT: processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.order"
我们从日志中可以看到:
- 冲突类型:
insert_exists(插入重复主键)。 - 冲突键:
order_id=10086,唯一索引order_pkey。 - 冲突来源:订阅端本地事务 740 在
15:20:30已插入该行(状态paid),而发布端同步的该行状态为unpaid。
定位冲突原因:经过业务层面分析,发现订阅端存在“本地补单”业务(因主库临时故障,手动在副本插入了order_id=10086的订单),导致主库恢复后同步时触发重复键冲突。
解决冲突:
若需保留主库数据(远程同步优先):删除订阅端本地的order_id=10086行,重启逻辑复制。
-- 订阅端执行:删除本地冲突行
DELETE FROM public.order WHERE order_id = 10086;
-- 重启逻辑复制(假设复制槽为 repl_slot)
SELECT pg_replication_slot_advance('repl_slot',
(SELECT confirmed_flush_lsn FROM pg_stat_replication WHERE slot_name = 'repl_slot'));
若需保留本地数据(补单有效):在主库删除order_id=10086行,重新同步。
非常不错的改进,感谢社区的所有相关人员。
参考
提交日志:https://git.postgresql.org/pg/commitdiff/9758174e2e5cd278cf37e0980da76b51890e0011