逻辑复制的行为类似于普通 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_type
DETAIL:detailed_explanation
. {detail_values
[; ... ]}. wheredetail_values
is 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
[, ...])}
日志提供以下信息:
LOG
schemaname
.tablename
标识冲突中涉及的本地关系。
conflict_type
是发生的冲突类型(例如,insert_exists
、update_exists
)。
DETAIL
detailed_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
。
如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步说明的内容,请使用 此表单 报告文档问题。