PostgreSQL 15: 允许发布模式中的表

John Doe 七月 9, 2025

想发布和订阅数据库中某个模式下的表吗?PostgreSQL 可以一键发布单个模式中的表了。

在山坡漫步的大象

特性提交日志

允许发布模式中的表。

CREATE/ALTER PUBLICATION 中新增了 “FOR ALL TABLES IN SCHEMA” 选项,允许指定一个或多个模式,发布者会选择这些模式中的表向订阅者发送数据。

新语法允许同时指定表和模式。例如:

CREATE PUBLICATION pub1 FOR TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;

或者

ALTER PUBLICATION pub1 ADD TABLE t1,t2,t3, ALL TABLES IN SCHEMA s1,s2;

新增了系统表 pg_publication_namespace,用于维护用户希望通过发布所发布的模式。修改了输出插件(pgoutput),以便在关系属于模式发布的一部分时发布更改。

更新了 pg_dump,以识别和转储模式的发布。更新了\d系列命令以显示模式的发布,且\dRp+命令现在也会显示相关的模式(如果有的话)。

讨论:https://www.postgresql.org/message-id/CALDaNm0OANxuJ6RXqwZsM1MSY4s19nuH3734j4a72etDwvBETQ@mail.gmail.com

示例

从 PostgreSQL 10 开始,我们就有了逻辑复制功能。从那以后,我们可以针对特定的一个或多个表,或者数据库中的所有表进行逻辑复制。

现在,我们在粒度上又进了一步,可以针对一个或多个模式中的所有表进行复制。

让我们看看它是如何工作的。为了测试,我们在同一台服务器上创建两个数据库,分别命名为 db_primary 和 db_replica:

$ psql -X -p 5430 -c 'create database db_primary'
$ psql -X -p 5431 -c 'create database db_replica'

然后,在每个数据库中,运行下面的 SQL 脚本:

CREATE  TABLE  public.alpha  ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);

CREATE SCHEMA bob;
CREATE SCHEMA carol;
CREATE SCHEMA dan;

CREATE  TABLE  bob.echo      ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  bob.foxtrot   ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  carol.golf    ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  carol.hotel   ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  carol.india   ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  dan.juliett   ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  dan.kilo      ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  dan.lima      ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);
CREATE  TABLE  dan.mike      ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);

这会创建 3 个模式,以及 10 个结构相同但名称不同的表,分布在 4 个模式中(3 个是新创建的,还有 1 个表在 public 模式中)。

现在,让我们在 db_primary 中创建一个发布,其中包含 “bob” 模式中的所有表、“dan” 模式中的部分表,以及 public 模式中的一个表:

create publication test_pub for table dan.juliett, dan.kilo, public.alpha, all tables in schema bob;
db_primary=# \dRp+
                           Publication test_pub
  Owner  | All tables | Inserts | Updates | Deletes | Truncates | Via root
---------+------------+---------+---------+---------+-----------+----------
 redrock | f          | t       | t       | t       | t         | f
Tables:
    "dan.juliett"
    "dan.kilo"
    "public.alpha"
Tables from schemas:
    "bob"

一切就绪,现在让我们从副本数据库进行订阅:

create subscription test_sub connection 'host=127.0.0.1 port=5430 dbname=db_primary user=postgres' publication test_pub;
NOTICE:  created replication slot "test_sub" on publisher

接下来,让我们进行测试:

insert into public.alpha (ts, payload) values (now(), 'initial value') returning *;
 id |              ts               |    payload    
----+-------------------------------+---------------
  1 | 2025-06-16 08:13:04.629613+08 | initial value
(1 row)

insert into public.alpha (ts, payload) values (now(), 'initial value') returning *;
 id |              ts               |    payload    
----+-------------------------------+---------------
  2 | 2025-06-16 08:13:15.073362+08 | initial value
(1 row)

update public.alpha set payload = 'second value' where id = 2;

select * from public.alpha;
 id |              ts               |    payload    
----+-------------------------------+---------------
  1 | 2025-06-16 08:13:04.629613+08 | initial value
  2 | 2025-06-16 08:13:15.073362+08 | second value
(2 rows)

看看副本数据库中的情况:

select * from public.alpha;
 id |              ts               |    payload    
----+-------------------------------+---------------
  1 | 2025-06-16 08:13:04.629613+08 | initial value
  2 | 2025-06-16 08:13:15.073362+08 | second value
(2 rows)

这很简单。那位于某个模式中的表呢?test_pub 包含了 bob 模式中的所有表,我们来看看:

insert into bob.echo (ts, payload) values (now(), 'xxx') returning *;
 id |              ts               | payload 
----+-------------------------------+---------
  1 | 2025-06-16 08:15:50.809102+08 | xxx
(1 row)

insert into bob.foxtrot (ts, payload) values (now(), 'yyy') returning *;
 id |              ts               | payload 
----+-------------------------------+---------
  1 | 2025-06-16 08:15:50.824428+08 | yyy
(1 row)

副本数据库中的情况:

select * from bob.echo;
 id |              ts               | payload
----+-------------------------------+---------
  1 | 2025-06-16 08:15:50.809102+08 | xxx
(1 row)

select * from bob.foxtrot;
 id |              ts               | payload
----+-------------------------------+---------
  1 | 2025-06-16 08:15:50.824428+08 | yyy
(1 row)

工作正常。接下来,让我们看看添加新表会发生什么。

$ psql -X -p 5430 -d db_primary -c "CREATE TABLE bob.november ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);"

$ psql -X -p 5431 -d db_replica -c "CREATE TABLE bob.november ( id int generated always as identity PRIMARY KEY, ts timestamptz, payload TEXT);"

完成上述操作后,让我们看看 november 表中的新数据是否会被复制:

$ psql -X -p 5430 -d db_primary -c "INSERT INTO bob.november (ts, payload) values (now(), 'cat') returning *"
 id |              ts               | payload
----+-------------------------------+---------
  1 | 2025-06-16 10:27:43.817067+08 | cat
(1 row)

验证一下:

$ psql -X -p 5431 -d db_replica -c "select * from bob.november"
 id | ts | payload 
----+----+---------
(0 rows)

为什么会这样呢?原因很简单。虽然发布中包含了所有数据,但订阅并不知道新表的存在。

要使其生效,我们需要在副本数据库上再执行一个操作:

alter subscription test_sub refresh publication;

然后,之前的数据立即就出现了:

select * from bob.november;
 id |              ts               | payload
----+-------------------------------+---------
  1 | 2025-06-16 10:27:43.817067+08 | cat
(1 row)

非常不错的特性,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/5a2832465fd8984d089e8c44c094e6900d987fcd