PostgreSQL 教程: 处理逻辑复制的冲突

十月 29, 2025

摘要:在本教程中,您将学习如何在 PostgreSQL 中处理逻辑复制的冲突问题。

目录

ALTER SUBSCRIPTION SKIP 命令

该命令的用途是在订阅端跳过在指定日志序列号(LSN)处完成的冲突事务。用户可以通过 “lsn” 跳过参数(SKIP 选项)指定完成时的日志序列号,以标识发生失败的事务。逻辑复制的应用工作进程(apply worker)从发布端接收更改,并判断在当前节点上是否应跳过这些更改。在使用 ALTER SUBSCRIPTION SKIP 命令时,我们可以直接使用错误消息上下文中的完成日志序列号。下面我们将通过一个演示来具体了解。

观察冲突及通过跳过事务解决冲突

1. 在发布端,创建表和发布(PUBLICATION)。

CREATE TABLE tab (id integer);
INSERT INTO tab VALUES (5);
CREATE PUBLICATION mypub FOR TABLE tab;

此时,我们已拥有一条用于表初始同步的记录。

2. 在订阅端,创建带有唯一约束的表和订阅(SUBSCRIPTION)。

CREATE TABLE tab (id integer UNIQUE);

CREATE SUBSCRIPTION mysub CONNECTION '...' PUBLICATION mypub WITH (disable_on_error = true);
NOTICE: created replication slot "mysub" on publisher

我们创建了一个启用了 “disable_on_error”(错误时禁用)选项的订阅。该订阅的定义会在后台触发表的初始同步,此过程将顺利完成,不会出现任何问题。

最终,订阅端会插入值 “5”。

3. 在发布端,在表同步完成后连续执行三个事务。

BEGIN; -- Txn1
INSERT INTO tab VALUES (1);
COMMIT;

BEGIN; -- Txn2
INSERT INTO tab VALUES (generate_series(2, 8));
COMMIT;

BEGIN; -- Txn3
INSERT INTO tab VALUES (9);
COMMIT;

SELECT * FROM tab;
 id
----
 5
 1
 2
 3
 4
 5
 6
 7
 8
 9
(10 rows)

在发布端,这些事务能够成功执行。但是,事务 2(Txn2)包含重复数据,该数据在表初始同步时已插入。因此,在订阅端,这会违反表上的唯一约束,并抛出错误。

4. 在订阅端,检查当前复制状态。

SELECT subname, subenabled FROM pg_subscription;
 subname | subenabled
---------+------------
 mysub   | f
(1 row)

SELECT * FROM tab;
 id
----
 5
 1
(2 rows)

现在我们来看一下订阅端的当前状态。根据 “disable_on_error” 选项的特性,发生错误后,订阅会自动被禁用。只有事务 1 以及表初始同步所复制的数据已在订阅端完成复制。由于冲突,我们无法看到事务 2 和事务 3 的执行结果。只有在解决冲突后,事务 3 才会被重新执行。

5. 在订阅端的日志中,我们可以看到此次冲突的错误信息以及 “disable_on_error” 选项的相关日志。

ERROR: duplicate key value violates unique constraint "tab_id_key"
DETAIL: Key (id)=(5) already exists.
CONTEXT: processing remote data for replication origin "pg_16389" during "INSERT" for replication target relation "public.tab" in transaction 730 finished at 0/1566D10
LOG: logical replication subscription "mysub" has been disabled due to an error

在订阅端的服务器日志中,我们可以获取到 ALTER SUBSCRIPTION SKIP 命令参数所需的 “完成日志序列号”(finish LSN)。下面我们来使用这个完成日志序列号来跳过事务 2。

6. 在订阅端,执行 ALTER SUBSCRIPTION SKIP 命令并启用订阅。

ALTER SUBSCRIPTION mysub SKIP (lsn = '0/1566D10');

SELECT subname, subskiplsn, subenabled FROM pg_subscription;
 subname | subskiplsn | subenabled
---------+------------+------------
 mysub   |  0/1566D10 | f
(1 row)

ALTER SUBSCRIPTION mysub ENABLE;

SELECT * FROM tab;
 id
----
 5
 1
 9
(3 rows)

SELECT subname, subskiplsn, subenabled FROM pg_subscription;
 subname | subskiplsn | subenabled
---------+------------+------------
 mysub   |      0/0   | t
(1 row)

此处,我们设置了跳过日志序列号(SKIP LSN)并重新激活了订阅。随即,我们可以看到事务 3 的复制结果(值为 9),而事务 2 的值并未出现,我们已跳过了整个事务 2。与此同时,在成功跳过更改后,“subskiplsn”(订阅跳过日志序列号)字段已被清空。

7. 在订阅端的日志中,我们可以看到该命令成功完成的日志信息。

LOG: start skipping logical replication transaction finished at 0/1566D10
CONTEXT: processing remote data for replication origin "pg_16389" during "BEGIN" in transaction 730 finished at 0/1566D10
LOG: done skipping logical replication transaction finished at 0/1566D10
CONTEXT: processing remote data for replication origin "pg_16389" during "COMMIT" in transaction 730 finished at 0/1566D10

我们也可以从服务器日志中看到这些成功执行的日志记录。

了解更多

PostgreSQL 管理