31.2. 订阅 #
订阅是逻辑复制的下游侧。定义订阅的节点称为订阅者。订阅定义了到另一个数据库的连接,以及要订阅的发布集合(一个或多个)。
订阅者数据库的行为与任何其他 PostgreSQL 实例相同,也可以通过定义自己的发布作为其他数据库的发布者。
订阅者节点可按需拥有多个订阅。也可以在同一对发布者-订阅者之间定义多个订阅,但必须注意确保被订阅的发布对象不重叠。
每个订阅都通过一个复制槽接收变更(见第 27.2.6 节)。初始同步已有表数据时可能需要额外复制槽,这些槽会在数据同步结束时删除。
逻辑复制订阅可以作为同步复制中的备库(见第 27.2.8 节)。其备库名称默认是订阅名。也可在订阅连接信息中通过 application_name 指定替代名称。
如果当前用户是超级用户,pg_dump 会转储订阅。否则会写出警告并跳过订阅,因为非超级用户无法从
pg_subscription 目录读取全部订阅信息。
通过 CREATE SUBSCRIPTION
添加订阅;可随时用
ALTER SUBSCRIPTION
停止/恢复;并可用
DROP SUBSCRIPTION
删除。
删除并重建订阅会丢失同步信息,这意味着之后必须重新同步数据。
模式定义不会被复制,已发布的表必须在订阅端存在。复制目标只能是普通表。例如,不能复制到视图。
发布端与订阅端通过表的完全限定名进行匹配。不支持复制到订阅端名称不同的表。
表的列也按名称匹配。订阅端表中列的顺序无需与发布端匹配。列的数据类型也无需匹配,只要数据的文本表示可以转换为目标类型即可。例如,可以从 integer 类型的列复制到 bigint 类型的列。目标表也可以有已发布表未提供的额外列。这些额外列将用目标表定义中指定的默认值填充。
31.2.1. 复制槽管理 #
如前所述,每个(活动)订阅都从远端(发布侧)的一个复制槽接收变更。
额外的表同步槽通常只会短暂存在,由系统内部创建用于执行初始表同步,在不再需要时自动删除。这些表同步槽的名称是自动生成的:“pg_%u_sync_%u_%llu”(参数分别为:订阅 oid、表 relid、系统标识符 sysid)。
通常,远端复制槽会在使用
CREATE SUBSCRIPTION
创建订阅时自动创建,并在使用
DROP SUBSCRIPTION
删除订阅时自动删除。但在某些场景下,分别操作订阅与底层复制槽会更有用或更必要。常见场景如下:
创建订阅时复制槽已存在。此时可使用
create_slot = false选项创建订阅并关联已有槽。创建订阅时远端主机不可达或状态不明确。此时可使用
connect = false选项创建订阅,系统将完全不连接远端主机。pg_dump 就采用这种方式。此后在订阅激活前,需要手工创建远端复制槽。删除订阅时希望保留复制槽。这在将订阅者数据库迁移到其他主机并从那里激活时很有用。此时应在尝试删除订阅前,使用
ALTER SUBSCRIPTION将槽与订阅解除关联。删除订阅时远端主机不可达。此时应在尝试删除订阅前使用
ALTER SUBSCRIPTION将槽与订阅解除关联。如果远端数据库实例已不存在,则无需进一步操作。但若只是暂时不可达,则应手工删除该复制槽(以及任何仍残留的表同步槽);否则这些槽会持续保留 WAL,最终可能导致磁盘被占满。这类情况应谨慎排查。
31.2.2. 示例 #
在发布端创建一些测试表。
test_pub=# CREATE TABLE t1(a int, b text, PRIMARY KEY(a)); CREATE TABLE test_pub=# CREATE TABLE t2(c int, d text, PRIMARY KEY(c)); CREATE TABLE test_pub=# CREATE TABLE t3(e int, f text, PRIMARY KEY(e)); CREATE TABLE
在订阅端创建同样的表。
test_sub=# CREATE TABLE t1(a int, b text, PRIMARY KEY(a)); CREATE TABLE test_sub=# CREATE TABLE t2(c int, d text, PRIMARY KEY(c)); CREATE TABLE test_sub=# CREATE TABLE t3(e int, f text, PRIMARY KEY(e)); CREATE TABLE
在发布端向表中插入数据。
test_pub=# INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three'); INSERT 0 3 test_pub=# INSERT INTO t2 VALUES (1, 'A'), (2, 'B'), (3, 'C'); INSERT 0 3 test_pub=# INSERT INTO t3 VALUES (1, 'i'), (2, 'ii'), (3, 'iii'); INSERT 0 3
为这些表创建发布。发布 pub2 和 pub3a
禁用了部分
publish
操作。发布 pub3b 使用了行过滤器(见第 31.3 节)。
test_pub=# CREATE PUBLICATION pub1 FOR TABLE t1; CREATE PUBLICATION test_pub=# CREATE PUBLICATION pub2 FOR TABLE t2 WITH (publish = 'truncate'); CREATE PUBLICATION test_pub=# CREATE PUBLICATION pub3a FOR TABLE t3 WITH (publish = 'truncate'); CREATE PUBLICATION test_pub=# CREATE PUBLICATION pub3b FOR TABLE t3 WHERE (e > 5); CREATE PUBLICATION
为这些发布创建订阅。订阅 sub3 同时订阅
pub3a 和 pub3b。默认情况下所有订阅都会复制初始数据。
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub1' test_sub-# PUBLICATION pub1; CREATE SUBSCRIPTION test_sub=# CREATE SUBSCRIPTION sub2 test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub2' test_sub-# PUBLICATION pub2; CREATE SUBSCRIPTION test_sub=# CREATE SUBSCRIPTION sub3 test_sub-# CONNECTION 'host=localhost dbname=test_pub application_name=sub3' test_sub-# PUBLICATION pub3a, pub3b; CREATE SUBSCRIPTION
注意,无论发布的 publish 操作如何,初始表数据都会被复制。
test_sub=# SELECT * FROM t1; a | b ---+------- 1 | one 2 | two 3 | three (3 rows) test_sub=# SELECT * FROM t2; c | d ---+--- 1 | A 2 | B 3 | C (3 rows)
此外,由于初始数据复制会忽略 publish 操作,且发布
pub3a 没有行过滤器,所以复制后的表 t3
会包含所有行,即使它们不匹配发布 pub3b 的行过滤器。
test_sub=# SELECT * FROM t3; e | f ---+----- 1 | i 2 | ii 3 | iii (3 rows)
在发布端向表继续插入数据。
test_pub=# INSERT INTO t1 VALUES (4, 'four'), (5, 'five'), (6, 'six'); INSERT 0 3 test_pub=# INSERT INTO t2 VALUES (4, 'D'), (5, 'E'), (6, 'F'); INSERT 0 3 test_pub=# INSERT INTO t3 VALUES (4, 'iv'), (5, 'v'), (6, 'vi'); INSERT 0 3
现在发布端数据如下:
test_pub=# SELECT * FROM t1; a | b ---+------- 1 | one 2 | two 3 | three 4 | four 5 | five 6 | six (6 rows) test_pub=# SELECT * FROM t2; c | d ---+--- 1 | A 2 | B 3 | C 4 | D 5 | E 6 | F (6 rows) test_pub=# SELECT * FROM t3; e | f ---+----- 1 | i 2 | ii 3 | iii 4 | iv 5 | v 6 | vi (6 rows)
注意在常规复制阶段会使用相应的 publish 操作。这意味着发布 pub2 与 pub3a 不会复制
INSERT。另外,发布 pub3b 只会复制与 pub3b 的行过滤器匹配的数据。现在订阅端数据如下:
test_sub=# SELECT * FROM t1; a | b ---+------- 1 | one 2 | two 3 | three 4 | four 5 | five 6 | six (6 rows) test_sub=# SELECT * FROM t2; c | d ---+--- 1 | A 2 | B 3 | C (3 rows) test_sub=# SELECT * FROM t3; e | f ---+----- 1 | i 2 | ii 3 | iii 6 | vi (4 rows)
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.