受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

29.2. 订阅 #

订阅是逻辑复制的下游侧。定义订阅的节点称为 订阅者。订阅定义了到另一个数据库的连接,以及要订阅的 发布集合(一个或多个)。

订阅者数据库的行为与任何其他 PostgreSQL 实例相同,也可以通过定义自己的发布 作为其他数据库的发布者。

订阅者节点可按需拥有多个订阅。也可以在同一对发布者-订阅者之间定义多个订阅, 但必须注意确保被订阅的发布对象不重叠。

每个订阅都通过一个复制槽接收变更(见 Section 26.2.6)。初始同步已有表数据时可能需要 额外复制槽,这些槽会在数据同步结束时删除。

逻辑复制订阅可以作为同步复制中的备库(见 Section 26.2.8)。其备库名称默认是订阅名。 也可在订阅连接信息中通过 application_name 指定替代名称。

如果当前用户是超级用户,pg_dump 会转储订阅。 否则会写出警告并跳过订阅,因为非超级用户无法从 pg_subscription 目录读取全部订阅信息。

通过 CREATE SUBSCRIPTION 添加订阅;可随时用 ALTER SUBSCRIPTION 停止/恢复;并可用 DROP SUBSCRIPTION 删除。

删除并重建订阅会丢失同步信息,这意味着之后必须重新同步数据。

模式定义不会被复制,已发布的表必须在订阅端存在。复制目标只能是普通表。 例如,不能复制到视图。

发布端与订阅端通过表的完全限定名进行匹配。不支持复制到订阅端名称不同的表。

表列也按名称匹配。订阅端表的列顺序无需与发布端一致。只要数据的文本表示可转换 为目标类型,列数据类型也无需一致。例如,可从 integer 列复制到 bigint 列。目标表还可包含发布表未提供的额外列,这些列会填充为目标 表定义中的默认值。不过,二进制格式逻辑复制限制更严格。详见 CREATE SUBSCRIPTIONbinary 选项。

29.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,最终可能导致磁盘被占满。 这类情况应谨慎排查。

29.2.2. 示例:建立逻辑复制 #

在发布端创建一些测试表。

/* pub # */ CREATE TABLE t1(a int, b text, PRIMARY KEY(a));
/* pub # */ CREATE TABLE t2(c int, d text, PRIMARY KEY(c));
/* pub # */ CREATE TABLE t3(e int, f text, PRIMARY KEY(e));

在订阅端创建同样的表。

/* sub # */ CREATE TABLE t1(a int, b text, PRIMARY KEY(a));
/* sub # */ CREATE TABLE t2(c int, d text, PRIMARY KEY(c));
/* sub # */ CREATE TABLE t3(e int, f text, PRIMARY KEY(e));

在发布端向表中插入数据。

/* pub # */ INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three');
/* pub # */ INSERT INTO t2 VALUES (1, 'A'), (2, 'B'), (3, 'C');
/* pub # */ INSERT INTO t3 VALUES (1, 'i'), (2, 'ii'), (3, 'iii');

为这些表创建发布。发布 pub2pub3a 禁用了部分 publish 操作。发布 pub3b 使用了行过滤器(见 Section 29.3)。

/* pub # */ CREATE PUBLICATION pub1 FOR TABLE t1;
/* pub # */ CREATE PUBLICATION pub2 FOR TABLE t2 WITH (publish = 'truncate');
/* pub # */ CREATE PUBLICATION pub3a FOR TABLE t3 WITH (publish = 'truncate');
/* pub # */ CREATE PUBLICATION pub3b FOR TABLE t3 WHERE (e > 5);

为这些发布创建订阅。订阅 sub3 同时订阅 pub3apub3b。默认情况下所有订阅都会复制 初始数据。

/* sub # */ CREATE SUBSCRIPTION sub1
/* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=sub1'
/* sub - */ PUBLICATION pub1;
/* sub # */ CREATE SUBSCRIPTION sub2
/* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=sub2'
/* sub - */ PUBLICATION pub2;
/* sub # */ CREATE SUBSCRIPTION sub3
/* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=sub3'
/* sub - */ PUBLICATION pub3a, pub3b;

注意,无论发布的 publish 操作如何,初始表数据都会被复制。

/* sub # */ SELECT * FROM t1;
 a |   b
---+-------
 1 | one
 2 | two
 3 | three
(3 rows)

/* sub # */ SELECT * FROM t2;
 c | d
---+---
 1 | A
 2 | B
 3 | C
(3 rows)

此外,由于初始数据复制会忽略 publish 操作,且发布 pub3a 没有行过滤器,所以复制后的表 t3 会包含所有行,即使它们不匹配发布 pub3b 的行过滤器。

/* sub # */ SELECT * FROM t3;
 e |  f
---+-----
 1 | i
 2 | ii
 3 | iii
(3 rows)

在发布端向表继续插入数据。

/* pub # */ INSERT INTO t1 VALUES (4, 'four'), (5, 'five'), (6, 'six');
/* pub # */ INSERT INTO t2 VALUES (4, 'D'), (5, 'E'), (6, 'F');
/* pub # */ INSERT INTO t3 VALUES (4, 'iv'), (5, 'v'), (6, 'vi');

现在发布端数据如下:

/* pub # */ SELECT * FROM t1;
 a |   b
---+-------
 1 | one
 2 | two
 3 | three
 4 | four
 5 | five
 6 | six
(6 rows)

/* pub # */ SELECT * FROM t2;
 c | d
---+---
 1 | A
 2 | B
 3 | C
 4 | D
 5 | E
 6 | F
(6 rows)

/* pub # */ SELECT * FROM t3;
 e |  f
---+-----
 1 | i
 2 | ii
 3 | iii
 4 | iv
 5 | v
 6 | vi
(6 rows)

注意在常规复制阶段会使用相应的 publish 操作。 这意味着发布 pub2pub3a 不会复制 INSERT。另外,发布 pub3b 只会复制与其 行过滤器匹配的数据。现在订阅端数据如下:

/* sub # */ SELECT * FROM t1;
 a |   b
---+-------
 1 | one
 2 | two
 3 | three
 4 | four
 5 | five
 6 | six
(6 rows)

/* sub # */ SELECT * FROM t2;
 c | d
---+---
 1 | A
 2 | B
 3 | C
(3 rows)

/* sub # */ SELECT * FROM t3;
 e |  f
---+-----
 1 | i
 2 | ii
 3 | iii
 6 | vi
(4 rows)

29.2.3. 示例:延迟创建复制槽 #

在某些场景(例如 Section 29.2.1)中,如果远端复制槽未自动创建, 用户必须在订阅激活前手工创建。下面示例展示了创建槽并激活订阅的步骤。 这些示例指定了标准逻辑解码输出插件 pgoutput,这是内置逻辑复制 使用的插件。

首先,创建示例所需的发布。

/* pub # */ CREATE PUBLICATION pub1 FOR ALL TABLES;

示例 1:订阅指定 connect = false

  • 创建订阅。

    /* sub # */ CREATE SUBSCRIPTION sub1
    /* sub - */ CONNECTION 'host=localhost dbname=test_pub'
    /* sub - */ PUBLICATION pub1
    /* 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 时未指定名称, 要创建的槽名与订阅名相同,例如 "sub1"。

    /* pub # */ SELECT * FROM pg_create_logical_replication_slot('sub1', 'pgoutput');
     slot_name |    lsn
    -----------+-----------
     sub1      | 0/19404D0
    (1 row)
    
  • 在订阅端完成订阅激活。之后 pub1 的表将开始复制。

    /* sub # */ ALTER SUBSCRIPTION sub1 ENABLE;
    /* sub # */ ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION;
    

示例 2:订阅指定 connect = false,并同时指定 slot_name 选项。

  • 创建订阅。

    /* sub # */ CREATE SUBSCRIPTION sub1
    /* sub - */ CONNECTION 'host=localhost dbname=test_pub'
    /* sub - */ PUBLICATION pub1
    /* 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 指定一致的名称手工创建槽, 例如 "myslot"。

    /* pub # */ SELECT * FROM pg_create_logical_replication_slot('myslot', 'pgoutput');
     slot_name |    lsn
    -----------+-----------
     myslot    | 0/19059A0
    (1 row)
    
  • 在订阅端,剩余激活步骤与前例相同。

    /* sub # */ ALTER SUBSCRIPTION sub1 ENABLE;
    /* sub # */ ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION;
    

示例 3:订阅指定 slot_name = NONE

  • 创建订阅。设置 slot_name = NONE 时,还需要设置 enabled = falsecreate_slot = false

    /* sub # */ CREATE SUBSCRIPTION sub1
    /* sub - */ CONNECTION 'host=localhost dbname=test_pub'
    /* sub - */ PUBLICATION pub1
    /* sub - */ WITH (slot_name=NONE, enabled=false, create_slot=false);
    
  • 在发布端使用任意名称手工创建槽,例如 "myslot"。

    /* pub # */ SELECT * FROM pg_create_logical_replication_slot('myslot', 'pgoutput');
     slot_name |    lsn
    -----------+-----------
     myslot    | 0/1905930
    (1 row)
    
  • 在订阅端,将订阅关联到刚创建的槽名。

    /* sub # */ ALTER SUBSCRIPTION sub1 SET (slot_name='myslot');
    
  • 剩余的订阅激活步骤与前例相同。

    /* sub # */ ALTER SUBSCRIPTION sub1 ENABLE;
    /* sub # */ ALTER SUBSCRIPTION sub1 REFRESH PUBLICATION;
    

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。