You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL 10逻辑复制触发器函数未执行问题排查

问题分析:逻辑复制下触发器未触发的原因及主主序列同步解决方案

首先直接解答你的核心疑问:为什么DB2的触发器在复制插入时没触发?

PostgreSQL逻辑复制的订阅端默认会跳过触发器、规则以及大部分约束检查(仅保留主键/唯一约束这类保证数据一致性的规则),这个行为由订阅的skip_triggers参数控制(默认值为true)。设计这个机制是为了避免源库已经执行过的业务逻辑(比如你的BEFORE INSERT触发器生成ID)在目标库重复执行,防止数据异常。所以当DB1的记录复制到DB2时,DB2上的update_ogc_fid_address触发器完全不会被触发,序列自然停留在原有位置,导致本地插入时仍使用旧值。

接下来针对你的主主架构需求,提供一套完整的解决方案,确保两边序列能自动同步到正确位置,且不会生成重复ID:


步骤1:修改触发器函数,区分本地插入与复制插入

我们需要让触发器只在本地插入时生成ID,复制过来的插入操作直接保留原ID。可以用pg_replication_origin_session_is_setup()函数判断当前操作是否来自逻辑复制:

CREATE OR REPLACE FUNCTION update_ogc_fid() 
RETURNS trigger 
LANGUAGE 'plpgsql' 
COST 100 VOLATILE NOT LEAKPROOF 
AS $BODY$ 
Declare 
    iInt integer; 
BEGIN 
    -- 如果是逻辑复制过来的操作,直接返回原记录,不生成新ID
    IF pg_replication_origin_session_is_setup() THEN
        RETURN NEW;
    END IF;
    
    -- 本地插入时,生成对应奇偶的ID
    SELECT nextval('address_ogc_fid_seq') INTO iInt; 
    NEW.ogc_fid = iInt; 
    RETURN NEW; 
END; 
$BODY$;

步骤2:创建序列同步的AFTER INSERT触发器

不管是本地插入还是复制插入,我们都需要确保序列的当前值不会小于已插入的最大ID,避免后续生成重复值。创建一个自动同步序列的触发器函数:

CREATE OR REPLACE FUNCTION sync_address_seq_after_insert()
RETURNS trigger
LANGUAGE plpgsql
AS $BODY$
DECLARE
    seq_start integer;
    seq_inc integer;
    desired_next_val integer;
BEGIN
    -- 获取当前序列的起始值和步长
    SELECT start_value, increment 
    INTO seq_start, seq_inc
    FROM pg_sequence
    WHERE seqrelid = 'address_ogc_fid_seq'::regclass;
    
    -- 计算下一个符合步长规则的、不小于当前插入ID的数值
    IF NEW.ogc_fid >= seq_start THEN
        desired_next_val = seq_start + ((NEW.ogc_fid - seq_start + seq_inc - 1) / seq_inc) * seq_inc;
    ELSE
        desired_next_val = seq_start;
    END IF;
    
    -- 只有当计算出的下一个值大于当前序列的nextval结果时,才更新序列
    IF desired_next_val > nextval('address_ogc_fid_seq') THEN
        -- 将序列当前值设置为 desired_next_val - 步长,确保nextval返回desired_next_val
        PERFORM setval('address_ogc_fid_seq', desired_next_val - seq_inc, false);
    END IF;
    
    RETURN NULL; -- AFTER触发器无需返回值
END;
$BODY$;

然后创建对应的AFTER触发器:

CREATE TRIGGER sync_address_seq_after_insert
AFTER INSERT ON address
FOR EACH ROW
EXECUTE PROCEDURE sync_address_seq_after_insert();

步骤3:修改订阅参数,允许触发器执行

因为我们需要让触发器在复制操作时也能执行(用来同步序列),所以要修改订阅的skip_triggers参数为false,两边的订阅都要修改:

在DB2上执行(对应DB1的订阅):

ALTER SUBSCRIPTION your_db1_subscription SET (skip_triggers = false);

在DB1上执行(对应DB2的订阅):

ALTER SUBSCRIPTION your_db2_subscription SET (skip_triggers = false);

步骤4:验证效果

  • 在DB1插入一条记录,会生成奇数ID(比如3),这条记录复制到DB2后,DB2的序列会自动同步到下一个偶数(4),后续DB2本地插入会生成4、6...
  • 在DB2插入一条记录,会生成偶数ID(比如2),复制到DB1后,DB1的序列会同步到下一个奇数(3),后续DB1本地插入会生成3、5...
  • 故障恢复后,两边的序列都会基于已插入的最大ID继续生成正确的奇偶ID,不会重复。

内容的提问来源于stack exchange,提问作者DevWizard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:28:59