PostgreSQL 18: 记录逻辑复制应用变更时的冲突

John Doe 十月 23, 2025

你知道如何处理 PostgreSQL 逻辑复制应用变更时的冲突问题吗?

image

特性提交日志

记录逻辑复制中应用变更时的冲突。

本补丁在应用变更的以下冲突场景中,提供了额外的日志信息:

  • insert_exists:插入的行违反了“不可延迟(NOT DEFERRABLE)”的唯一约束。
  • update_differ:待更新的行此前已被其他来源(如本地业务、另一发布端)修改。
  • update_exists:行更新后的值违反了“不可延迟(NOT DEFERRABLE)”的唯一约束。
  • update_missing:待更新的元组(行数据)不存在。
  • delete_differ:待删除的行此前已被其他来源修改。
  • delete_missing:待删除的元组(行数据)不存在。

对于insert_existsupdate_exists冲突,若启用了track_commit_timestamp配置,日志中可包含冲突键的来源(修改源)及提交时间戳详情。

仅当订阅端启用track_commit_timestamp配置时,才能检测到update_differdelete_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