在分布式数据库架构中,将多个PostgreSQL实例通过逻辑复制连接成环状拓扑,能够实现就近写入与异地容灾。但当节点A的修改同步到节点B,再经节点C最终回流到节点A时,如果没有防循环机制,这条记录会被再次回放,引发无限循环复制。解决该问题的核心在于让每个节点具备识别“哪些数据是自己发出的”的能力,从而在接收端主动丢弃回流事务。

环状复制产生循环的根本原因
PostgreSQL的逻辑复制基于发布(publication)与订阅(subscription)模型,主节点将预写日志(WAL)中符合条件的数据变更抽取为逻辑解码消息,通过walsender发送给订阅节点的apply worker。在单向复制中,目标端不会反向推送,因此不存在循环。但在环状结构中,假设有节点N1、N2、N3,N1发布给N2,N2发布给N3,N3又发布回N1,那么N1上原始插入的一行,会依次在N2、N3重放,最终以“新事务”形态回到N1。
由于逻辑复制默认不携带全局事务溯源标识,N1在收到N3转发来的这条记录时,从数据库内核视角看,它和本地业务直接插入的记录没有任何区别。apply worker会正常执行INSERT,若表上定义了主键或唯一索引,就可能抛出重复键错误;若没有唯一约束,则会出现数据重复,并且这次插入又会作为N1的新变更继续向N2传播,形成雪崩式循环。很多团队在测试环境数据量小的时候不易察觉,一旦上生产就引发磁盘迅速占满。
除了数据重复,循环复制还会造成序列错乱和触发器误触发。如果表上配置了行级触发器,回流数据可能再次激活触发器逻辑,比如发送消息到队列或更新统计字段,导致业务逻辑被意外执行多次。因此,防循环不是可选项,而是环状拓扑的必备基础能力。
基于发布过滤与schema隔离的防循环方案
一种实用且对业务侵入较小的方法是给每个节点分配独立的schema前缀。例如N1写入的表全部位于node1 schema下,N2写入node2 schema下。各节点的发布仅包含自身schema,这样N3从N2收到node2的数据后,在其发布中只暴露node3 schema,绝不会把node2的表再推回N1。通过物理分库分schema,从发布源头掐断了跨节点回流的路径。
在配置发布时,可以使用以下SQL仅发布本地schema。注意这里谈论的<publication>是逻辑复制对象,需要用转义形式表示标签名。实际命令如下:
-- 在节点N1上执行 CREATE PUBLICATION pub_n1 FOR TABLES IN SCHEMA node1; -- 在节点N2上执行 CREATE PUBLICATION pub_n2 FOR TABLES IN SCHEMA node2; -- 在节点N3上执行 CREATE PUBLICATION pub_n3 FOR TABLES IN SCHEMA node3;
订阅端则正常订阅上游节点的发布。因为N1的订阅只对接N3的pub_n3,而N3的发布里没有node1的表,所以N1永远不会收到自己发出的数据。该方案优点是非常直观,不需要修改应用代码;缺点是要求业务层严格按节点划分schema,对老系统改造难度较大,且跨节点联合查询需通过视图封装。
如果无法接受schema拆分,也可以在发布中使用行过滤器(row filter)。PostgreSQL 15及以上支持在发布中通过WHERE子句过滤,比如每个节点写入时打上origin_node字段,发布定义为WHERE origin_node = 'N1',这样只有本节点产生的行才会被发布,其他节点转发来的行因为该字段不匹配而被排除。示例如下:
CREATE PUBLICATION pub_n1 FOR TABLE orders WHERE (origin_node = 'N1');
利用逻辑解码插件与origin标识增强防护
当使用pgoutput以外的自定义解码插件时,可以在消息头中注入origin_id。每个节点在启动订阅前,在postgresql.conf中配置唯一的cluster_name,解码插件将之写入变更事件。订阅端的apply钩子检查事件中的origin_id,若等于本地标识则跳过。这种方式把防循环逻辑下沉到中间件层,对SQL层透明。
原生逻辑复制也提供了disable_on_error与slot的配合来降低风险。虽然不能直接识别循环,但可以通过监控pg_stat_subscription中的落后字节数,一旦检测到异常增长就自动停用订阅,避免循环恶化。以下查询可观察复制状态:
SELECT subname, received_lsn, latest_end_lsn,
(received_lsn <> latest_end_lsn) AS lagging
FROM pg_stat_subscription;
此外,务必正确设置replica identity。环状复制中,更新和删除操作需要旧值来定位目标行,若表未设置REPLICA IDENTITY FULL或主键,apply端可能匹配不到行而报错,进而中断复制。应在每个节点对核心表执行:
ALTER TABLE orders REPLICA IDENTITY FULL;
综合来看,防循环的最佳实践是“发布层过滤为主、监控熔断为辅”。在新建系统时就规划好节点与schema或行标识的对应关系,可免去后期复杂治理。对于已运行系统,先通过只读订阅做回流演练,确认无循环后再切为双向可写,能大幅降低线上事故概率。
PostgreSQL逻辑复制环状复制修改时间:2026-08-16 03:50:13