订阅 是逻辑复制的下游。定义订阅的节点称为 订户。订阅定义通往另一个数据库和一组发布(一个或多个)的连接,订户将订阅这些发布。
订户数据库的行为与任何其他 PostgreSQL 实例相同,并且可以通过定义其自己的发布而用作其他数据库的发布者。
如果需要,订户节点可以有多个订阅。在单个发布者-订户对之间,可以定义多个订阅,在这种情况下,必须注意确保订阅的发布对象不重叠。
每个订阅都将通过一个复制槽接收更改(请参见 第 26.2.6 节)。可能需要额外的复制槽来同步现有表数据中的初始数据,并在数据同步结束时删除这些复制槽。
逻辑复制订阅可以充当同步复制的备用(请参阅第 26.2.8 节)。备用名称默认为订阅名称。可以在订阅的连接信息中将备用名称指定为application_name
。
如果当前用户是超级用户,那么pg_dump
将转储订阅。否则将生成一个警告并且将跳过订阅,因为非超级用户无法从pg_subscription
目录读取所有订阅信息。
使用CREATE SUBSCRIPTION
添加订阅,并且可以使用ALTER SUBSCRIPTION
命令随时停止/恢复订阅,并且可以使用DROP SUBSCRIPTION
删除订阅。
当删除订阅然后重新创建时,将丢失同步信息。这意味着必须重新同步数据。
不会复制架构定义,并且已发布的表必须存在于订户上。只有常规表可以成为复制的目标。例如,不能复制到视图。
使用完整限定表名称在发布服务器和订户之间匹配表。不支持复制到订户上不同名称的表。
表中的列也按名称匹配。订户表中列的顺序不必与发布服务器中的顺序匹配。只要可以将数据的文本表示转换成目标类型,则列的数据类型不必匹配。例如,可以从类型为integer
的列复制到类型为bigint
的列。目标表还可以具有已发布表未提供的其他列。任何此类列都将填充目标表定义中指定的默认值。但二进制格式的逻辑复制更具限制性。有关详细信息,请参见CREATE SUBSCRIPTION
的binary
选项。
如前所述,每个(活动)订阅都会从远程(发布)端上的复制槽位接收更改。
其他表格同步槽通常是瞬态的,在内部创建用于执行初始表格同步,当不再需要时自动删除。这些表格同步槽具有生成名称:“pg_%u_sync_%u_%llu
”(参数:订阅oid
、表格relid
、系统标识符sysid
)
一般情况下,在使用 CREATE SUBSCRIPTION
创建订阅时,会自动创建远程复制槽,且在使用 DROP SUBSCRIPTION
删除订阅时,会自动删除该远程复制槽。但在某些情况下,单独处理订阅和底层复制槽非常有用或很有必要。以下为一些情况
创建订阅时,复制槽已存在。在这种情况下,可以使用 create_slot = false
选项创建订阅,以关联到已存在的槽。
创建订阅时,无法访问远程主机或远程主机处于不明确状态。在这种情况下,可以使用 connect = false
选项创建订阅。之后,将完全不会联系远程主机。这是 pg_dump 所使用的。因此,必须在激活订阅之前手动创建远程复制槽。
删除订阅时,应保留复制槽。在将订阅数据库移至不同的主机并将从那里激活数据库时,这样做很有用。在这种情况下,在尝试删除订阅之前,请使用 ALTER SUBSCRIPTION
取消关联订阅与槽。
删除订阅时,无法访问远程主机。在这种情况下,在尝试删除订阅之前,请使用 ALTER SUBSCRIPTION
取消关联订阅与槽。如果远程数据库实例不再存在,则无需执行进一步的操作。但是,如果远程数据库实例只是处于不可访问状态,则应手动删除复制槽(和任何仍保留的表格同步槽);否则,它们将继续保留 WAL,并可能最终导致磁盘空间不足。应仔细调查此类案例。
在发布者上创建一些测试表。
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
有行筛选器(参见 第 29.4 节)。
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)
在某些情况下(例如,第 29.2.1 节),如果远程复制槽未自动创建,用户必须在激活订阅信息之前手动创建它。在以下示例中显示了创建槽和激活订阅信息所需的步骤。这些示例指定了内置逻辑复制使用的标准逻辑解码输出插件 (pgoutput
)。
首先,为要使用的示例创建发布信息。
test_pub=# CREATE PUBLICATION pub1 FOR ALL TABLES; CREATE PUBLICATION
示例 1:订阅信息告知 connect = false
创建订阅信息。
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub' test_sub-# PUBLICATION pub1 test_sub-# WITH (connect=false); WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. CREATE SUBSCRIPTION
在发布者上手动创建一个槽。因为未在 CREATE SUBSCRIPTION
期间指定名称,所以要创建的槽的名称与订阅信息名称相同,例如“sub1”。
test_pub=# SELECT * FROM pg_create_logical_replication_slot('sub1', 'pgoutput'); slot_name | lsn -----------+----------- sub1 | 0/19404D0 (1 row)
在订阅者上,完成订阅信息激活。此后,pub1
的表格将开始复制。
test_sub=# ALTER SUBSCRIPTION sub1 ENABLE; ALTER SUBSCRIPTION test_sub=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION; ALTER SUBSCRIPTION
示例 2:如果订阅指定 connect = false
,但还指定了 slot_name
选项。
创建订阅信息。
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub' test_sub-# PUBLICATION pub1 test_sub-# WITH (connect=false, slot_name='myslot'); WARNING: subscription was created, but is not connected HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. CREATE SUBSCRIPTION
在发布者上,使用在 CREATE SUBSCRIPTION
期间指定的相同名称(例如 “myslot”)手动创建槽。
test_pub=# SELECT * FROM pg_create_logical_replication_slot('myslot', 'pgoutput'); slot_name | lsn -----------+----------- myslot | 0/19059A0 (1 row)
在订阅者上,其余订阅激活步骤与以前相同。
test_sub=# ALTER SUBSCRIPTION sub1 ENABLE; ALTER SUBSCRIPTION test_sub=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION; ALTER SUBSCRIPTION
示例 3:订阅指定 slot_name = NONE
的情况
创建订阅。当 slot_name = NONE
时,还需要 enabled = false
和 create_slot = false
。我
test_sub=# CREATE SUBSCRIPTION sub1 test_sub-# CONNECTION 'host=localhost dbname=test_pub' test_sub-# PUBLICATION pub1 test_sub-# WITH (slot_name=NONE, enabled=false, create_slot=false); CREATE SUBSCRIPTION
在发布者上,使用任何名称(例如,"myslot")手动创建一个插槽。
test_pub=# SELECT * FROM pg_create_logical_replication_slot('myslot', 'pgoutput'); slot_name | lsn -----------+----------- myslot | 0/1905930 (1 row)
在订阅者上,将订阅与刚刚创建的槽名称关联。
test_sub=# ALTER SUBSCRIPTION sub1 SET (slot_name='myslot'); ALTER SUBSCRIPTION
其余订阅激活步骤与以前相同。
test_sub=# ALTER SUBSCRIPTION sub1 ENABLE; ALTER SUBSCRIPTION test_sub=# ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION; ALTER SUBSCRIPTION