PostgreSQL 19: 逻辑复制中的序列同步

John Doe 十一月 19, 2025

PostgreSQL 的逻辑复制已经支持很久了,只是一直缺少对于序列同步的支持。

image

特性提交日志

为发布添加 “ALL SEQUENCES” 支持。

此补丁为发布添加了ALL SEQUENCES子句支持,实现所有序列的同步/复制,对版本升级场景十分实用。

现在,发布可通过FOR ALL SEQUENCES包含所有序列。

psql 增强功能:

  • \d命令会显示指定序列关联的发布。
  • \dRp命令会指示发布是否包含所有序列。

ALL SEQUENCES可与ALL TABLES组合使用,但不能与TABLETABLES IN SCHEMA等其他选项混用。未来我们可能会扩展支持更精细的子句。

pg_publication_sequences视图提供发布与序列之间的映射信息。

此补丁实现了序列的发布功能;订阅端支持将在后续补丁中添加。

讨论:https://postgr.es/m/CAA4eK1LC+KJiAkSrpE_NwvNdidw9F2os7GERUeSxSKv71gXysQ@mail.gmail.com

示例

在发布端创建数据库 seq_test_src:

CREATE DATABASE seq_test_src;

在订阅端创建数据库 seq_test_dest:

CREATE DATABASE seq_test_dest;

接下来,在两个数据库中分别执行以下 SQL:

CREATE TABLE test_table_serial (
    id serial primary key,
    payload text
);

CREATE TABLE test_table_identity (
    id int8 generated always as identity primary key,
    payload text
);

CREATE SEQUENCE standalone_seq;

现在我们开始配置复制。在发布端(seq_test_src 数据库)执行以下命令即可:

CREATE PUBLICATION test_pub FOR ALL TABLES, ALL SEQUENCES;

在订阅端(seq_test_dest 数据库)执行:

CREATE SUBSCRIPTION test_sub
CONNECTION 'host=127.0.0.1 port=5430 dbname=seq_test_src user=replicator'
PUBLICATION test_pub;
NOTICE:  created replication slot "test_sub" on publisher

配置完成。让我们看看它的工作效果。首先在发布端(seq_test_src 数据库)插入一些数据,同时确保操作未绑定到任何表的独立序列:

INSERT INTO test_table_serial (payload) VALUES ('a'), ('b');
INSERT INTO test_table_identity (payload) VALUES ('c'), ('d'), ('e');

WITH vals AS (SELECT nextval('standalone_seq') AS v FROM generate_series(1,10) i)
SELECT count(*), min(v), max(v) FROM vals;

执行成功,最后的查询结果如下:

 count | min | max
-------+-----+-----
    10 |   1 |  10
(1 row)

此时,查看发布端(seq_test_src 数据库)的序列状态:

SELECT * FROM standalone_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
         10 |      23 | t
(1 row)

SELECT * FROM test_table_identity_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          3 |      30 | t
(1 row)

SELECT * FROM test_table_serial_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          2 |      31 | t
(1 row)

再查看订阅端(seq_test_dest 数据库)的序列状态:

SELECT * FROM standalone_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          1 |       0 | f
(1 row)

SELECT * FROM test_table_identity_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          1 |       0 | f
(1 row)

SELECT * FROM test_table_serial_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          1 |       0 | f
(1 row)

结果有点出乎意料。为什么last_value都是1呢?

让我们查看pg_subscription_rel中订阅的状态:

SELECT
    c.relname,
    r.srsubstate
FROM
    pg_subscription AS s
    JOIN pg_subscription_rel AS r ON s.oid = r.srsubid
    JOIN pg_class AS c ON r.srrelid = c.oid
WHERE
    s.subname = 'test_sub' AND
    c.relkind = 'S';
          relname            srsubstate
----------------------------+------------
 test_table_serial_id_seq    r
 test_table_identity_id_seq  r
 standalone_seq              r
(3 rows)

通过查看特性提交日志发现,只有当序列状态为INIT时才会进行同步。

幸运的是,我们可以在订阅端(seq_test_dest 数据库),通过以下命令手动触发同步:

ALTER SUBSCRIPTION test_sub REFRESH SEQUENCES;

执行后,查看订阅端(seq_test_dest 数据库),得到了预期结果:

SELECT * FROM standalone_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
         14 |       0 | t
(1 row)

SELECT * FROM test_table_identity_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          3 |       0 | t
(1 row)

SELECT * FROM test_table_serial_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          2 |       0 | t
(1 row)

需要说明的是,last_value与之前的预期略有不同,因为在测试过程中额外调用了几次nextval()

由此可见,序列同步不会自动触发,需要手动调用命令,但执行速度很快,而且省去了大量手动排查“需从哪个表获取 ID 来更新哪个序列”的工作。

非常不错的改进,感谢社区的所有相关人员。这无疑将大大简化逻辑复制在版本升级场景中的使用。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/96b37849734673e7c82fb86c4f0a46a28f500ac8