十月 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
我们也可以从服务器日志中看到这些成功执行的日志记录。