邏輯複製的行為類似於普通 DML 操作,即使資料在訂閱節點上已被本地更改,資料仍會被更新。如果傳入的資料違反了任何約束,複製將停止。這被稱為“衝突”。在複製 UPDATE 或 DELETE 操作時,缺少資料也被視為“衝突”,但不會導致錯誤,並且此類操作將被簡單地跳過。
在以下“衝突”情況下,會觸發額外的日誌記錄,並收集衝突統計資訊(顯示在 pg_stat_subscription_stats 檢視中):
insert_exists #插入一條違反 NOT DEFERRABLE 唯一約束的行。請注意,要記錄衝突鍵的源和提交時間戳詳細資訊,應在訂閱端啟用 track_commit_timestamp。在這種情況下,將引發錯誤,直到衝突手動解決。
update_origin_differs #更新一條以前被另一個源修改過的行。請注意,只有在訂閱端啟用了 track_commit_timestamp 時才能檢測到此衝突。目前,無論本地行的來源如何,更新都會被應用。
update_exists #行的更新值違反了 NOT DEFERRABLE 唯一約束。請注意,要記錄衝突鍵的源和提交時間戳詳細資訊,應在訂閱端啟用 track_commit_timestamp。在這種情況下,將引發錯誤,直到衝突手動解決。請注意,在更新分割槽表時,如果更新後的行值滿足另一個分割槽約束,導致該行被插入到新分割槽中,如果新行違反了 NOT DEFERRABLE 唯一約束,則可能會出現 insert_exists 衝突。
update_missing #要更新的行未找到。在此情況下,更新將被簡單地跳過。
delete_origin_differs #刪除一條以前被另一個源修改過的行。請注意,只有在訂閱端啟用了 track_commit_timestamp 時才能檢測到此衝突。目前,無論本地行的來源如何,刪除都會被應用。
delete_missing #要刪除的行未找到。在此情況下,刪除將被簡單地跳過。
multiple_unique_conflicts #插入或更新一條行違反了多個 NOT DEFERRABLE 唯一約束。請注意,要記錄衝突鍵的源和提交時間戳詳細資訊,請確保在訂閱端啟用了 track_commit_timestamp。在這種情況下,將引發錯誤,直到衝突手動解決。
請注意,還存在其他衝突場景,例如排他約束違反。目前,我們不為它們提供額外的日誌詳細資訊。
邏輯複製衝突的日誌格式如下:
LOG: conflict detected on relation "schemaname.tablename": conflict=conflict_typeDETAIL:detailed_explanation. {detail_values[; ... ]}. wheredetail_valuesis one of:Key(column_name[, ...])=(column_value[, ...])existing local row[(column_name[, ...])=](column_value[, ...])remote row[(column_name[, ...])=](column_value[, ...])replica identity{(column_name[, ...])=(column_value[, ...]) | full [(column_name[, ...])=](column_value[, ...])}
日誌提供以下資訊:
LOGschemaname.tablename 標識衝突中涉及的本地關係。
conflict_type 是發生的衝突型別(例如,insert_exists、update_exists)。
DETAILdetailed_explanation 包括修改現有本地行的事務的源、事務 ID 和提交時間戳(如果可用)。
Key 部分包含對於 insert_exists、update_exists 或 multiple_unique_conflicts 衝突違反唯一約束的本地行的鍵值。
existing local row 部分包含本地行,如果其源與遠端行不同(對於 update_origin_differs 或 delete_origin_differs 衝突),或者如果鍵值與遠端行衝突(對於 insert_exists、update_exists 或 multiple_unique_conflicts 衝突)。
remote row 部分包含導致衝突的遠端插入或更新操作的新行。請注意,對於更新操作,如果值未更改並且已進行 toasted,則新行的列值將為 null。
replica identity 部分包含用於搜尋要更新或刪除的現有本地行的副本身份鍵值。如果本地關係被標記為 REPLICA IDENTITY FULL,則可能包括整行值。
column_name 是列名。對於 existing local row、remote row 和 replica identity full 的情況,僅當用戶無權訪問表的所有列時,才會記錄列名。如果存在列名,則它們出現的順序與相應的列值相同。
column_value 是列值。大型列值將被截斷為 64 位元組。
請注意,在 multiple_unique_conflicts 衝突的情況下,將生成多個 detailed_explanation 和 detail_values 行,每行都詳細說明與不同唯一約束相關的衝突資訊。
邏輯複製操作以擁有訂閱的角色所擁有的特權執行。目標表上的許可權失敗將導致複製衝突,目標表上啟用的行級安全策略(訂閱所有者受其約束)也會導致複製衝突,而無論任何策略是否會拒絕正在複製的 INSERT、UPDATE、DELETE 或 TRUNCATE。此行級安全限制可能會在未來的 PostgreSQL 版本中解除。
產生錯誤的衝突將停止複製;使用者必須手動解決。有關衝突的詳細資訊可以在訂閱端的伺服器日誌中找到。
解決方案可以透過修改訂閱端的資料或許可權使其不與傳入更改衝突,或者透過跳過與現有資料衝突的事務來完成。當衝突產生錯誤時,複製將不會繼續,邏輯複製工作程序會向訂閱端的伺服器日誌發出以下型別的訊息:
ERROR: conflict detected on relation "public.test": conflict=insert_exists DETAIL: Key already exists in unique index "t_pkey", which was modified locally in transaction 740 at 2024-06-26 10:47:04.727375+08. Key (c)=(1); existing local row (1, 'local'); remote row (1, 'remote'). CONTEXT: processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/14C0378
包含違反約束的更改的事務的 LSN 和複製源名稱可以從伺服器日誌中找到(在上述情況下,LSN 為 0/14C0378,複製源為 pg_16395)。可以使用 ALTER SUBSCRIPTION ... SKIP 和結束 LSN(即 0/14C0378)來跳過產生衝突的事務。結束 LSN 可以是事務在釋出端提交或準備的 LSN。或者,也可以透過呼叫 pg_replication_origin_advance() 函式來跳過事務。在使用此函式之前,需要暫時停用訂閱,可以透過 ALTER SUBSCRIPTION ... DISABLE 來實現,或者可以使用 disable_on_error 選項來使用訂閱。然後,您可以使用 pg_replication_origin_advance() 函式,並提供 node_name(即 pg_16395)和結束 LSN 的下一個 LSN(即 0/14C0379)。可以使用 pg_replication_origin_status 系統檢視檢視源的當前位置。請注意,跳過整個事務包括跳過可能不違反任何約束的更改。這很容易導致訂閱端不一致。關於衝突行的其他詳細資訊,例如它們的源和提交時間戳,可以在日誌的 DETAIL 行中找到。但請注意,只有當訂閱端啟用了 track_commit_timestamp 時,此資訊才可用。使用者可以使用此資訊來決定保留本地更改還是採用遠端更改。例如,上述日誌中的 DETAIL 行表明現有行在本地被修改過。使用者可以手動執行“遠端更改優先”的操作。
當 streaming 模式為 parallel 時,失敗事務的結束 LSN 可能不會被記錄。在這種情況下,可能需要將流模式更改為 on 或 off 並再次引發相同的衝突,以便失敗事務的結束 LSN 會寫入伺服器日誌。有關結束 LSN 的用法,請參考 ALTER SUBSCRIPTION ... SKIP。
如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步說明的內容,請使用 此表單 報告文件問題。