由 John Doe 十一月 19, 2025
PostgreSQL 的逻辑复制已经支持很久了,只是一直缺少对于序列同步的支持。

特性提交日志
为发布添加 “ALL SEQUENCES” 支持。
此补丁为发布添加了ALL SEQUENCES子句支持,实现所有序列的同步/复制,对版本升级场景十分实用。
现在,发布可通过FOR ALL SEQUENCES包含所有序列。
psql 增强功能:
\d命令会显示指定序列关联的发布。\dRp命令会指示发布是否包含所有序列。
ALL SEQUENCES可与ALL TABLES组合使用,但不能与TABLE或TABLES 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