PostgreSQL码农集散地

PostgreSQL 19 - 逻辑订阅端冲突检测增强

PostgreSQL 19 preview - 逻辑订阅端冲突检测增强后续, 检测并报告 update_deleted 冲突

之前的patch刚发了一个框架, 现在后续的功能来了. 支持检测并报告 update_deleted 冲突.

《PostgreSQL 19 preview - 逻辑订阅端冲突检测增强》

Detect and report update_deleted conflicts.

https://github.com/postgres/postgres/commit/fd5a1a0c3e566f7fc860838084466a1c25944281

检测并报告 update_deleted 冲突。

主要内容

该提交增强了 PostgreSQL 逻辑复制的冲突检测能力,能够在订阅端检测并报告“update_deleted”类型的冲突。这种冲突指的是远端节点尝试更新一行,但该行在本地已被其他节点删除的情况。

核心流程:

  • 在 retain_dead_tuples 选项启用时,订阅端会保留被删除元组的信息(例如,删除事务的ID、时间戳和origin)。
  • 当远端 update 操作在本地找不到对应行时,会额外进行一次全表扫描(SnapshotAny),查找最近被删除但尚未被VACUUM清理的、且符合old值的tuple。
  • 如果发现这样的元组,且删除操作来自不同的origin,则报告 update_deleted 冲突,并记录相关事务及origin信息。
  • 统计信息及监控接口也增加了对这种冲突的计数。

影响的主要文件和逻辑

  • 文档:增加了 update_deleted 类型的冲突说明,retain_dead_tuples 选项的行为描述也相应更新。
  • 监控视图和统计函数:如 pg_stat_subscription_stats,多了 confl_update_deleted 字段。
  • 复制执行和冲突检测代码大幅增强,支持通过索引或全表扫描查找最近被删除的tuple,比较commit timestamp判断最新的删除。

适用场景举例

场景描述

  • 集群中有A、B两个节点。
  • 用户在A节点发起了 UPDATE tab SET ... WHERE id=1,此时B节点已经将 id=1 这行删除。
  • 如果开启了 retain_dead_tuples,当A节点的update操作通过逻辑复制同步到B节点时,B节点会检测到本地该行已被其他origin删除——此时不会直接报“找不到(update_missing)”,而是更精确地记录为“update_deleted”冲突,并跳过该update。

关键参数

  • retain_dead_tuples:订阅时需开启此参数。
  • track_commit_timestamp:必须全局开启。
  • 相关文档参见 conflict-update-deleted 章节。

SQL 示例

  1. 创建发布与订阅时开启参数:
-- 发布端  
CREATE PUBLICATION pub FORTABLE tab;  
-- 订阅端,注意开启 retain_dead_tuples  
CREATE SUBSCRIPTION sub CONNECTION'...' PUBLICATION pub WITH (retain_dead_tuples = true);  
  1. 检查统计信息(比如订阅端):
SELECT confl_update_deleted   
FROM pg_stat_subscription_stats   
WHERE subid = (SELECToidFROM pg_subscription WHERE subname = 'sub');  
  1. 典型冲突日志示例(订阅端日志):
conflict detected on relation "public.tab": conflict=update_deleted ...  
DETAIL: The row to be updated was deleted locally in transaction 12345 at 2025-08-04 12:34:56+00.  
Remote tuple (1, 3); replica identity (a)=(1)  

测试用例参考

详见源码中的 src/test/subscription/t/035_conflicts.pl,有如下自动化测试断言:

ok( $logfile =~  
qr/conflict detected on relation "public.tab": conflict=update_deleted.*/
  .*DETAIL:.* The row to be updated was deleted locally in transaction [0-9]+ at .*/,  
  'update target row was deleted in tab');  

总结

此提交让 PostgreSQL 的逻辑复制在多主/双向复制场景下,能更准确地检测并区分 update_missing 与 update_deleted 冲突,提高了数据一致性保障能力,并为后续更智能的冲突自动解决打下基础。