2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11 / 10

29.2. 訂閱 #

一個 訂閱 是邏輯複製的下游。定義訂閱的節點稱為 訂閱者。訂閱定義了與另一個數據庫的連線以及它想要訂閱的一個或多個釋出。

訂閱者資料庫的行為與其他任何 PostgreSQL 例項相同,並且可以透過定義自己的釋出來作為其他資料庫的釋出者。

訂閱者節點可以有多個訂閱。可以在單個釋出者-訂閱者對之間定義多個訂閱,在這種情況下,必須小心確保訂閱的釋出物件不重疊。

每個訂閱將透過一個複製槽接收更改(參見 第 26.2.6 節)。可能需要額外的複製槽來進行預先存在的表資料的初始資料同步,這些同步槽將在資料同步結束時被刪除。

邏輯複製訂閱可以作為同步複製的備用(參見 第 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 有一個行過濾器(參見 第 29.4 節)。

/* 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 只會複製匹配 pub3b 行過濾器的 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. 示例: 延遲複製槽建立 #

在某些情況下(例如 第 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;
    

提交更正

如果您在文件中看到任何不正確、與您對特定功能的使用體驗不符或需要進一步澄清的內容,請使用 此表格 報告文件問題。