合并复制如何检测和解决冲突

合并复制允许多个节点进行自主数据更改,因此存在这样的情况:一个节点上对数据的更改可能与另一个节点上对相同数据的更改相冲突。 在其他情况中,合并代理会遇到如约束冲突这样的错误,因此无法将特定节点上的更改传播到另一个节点。 本主题描述了冲突类型、如何检测和解决冲突,以及影响检测和解决方案的因素。

检测和解决冲突

合并代理使用 MSmerge_contents 系统表的 lineage 列检测冲突;如果对项目启用了列级跟踪,则还需要使用 COLV1 列。 这些列包含的元数据涉及何时插入或更新行或列,以及合并复制拓扑中哪些节点会更改行或列。 可以使用系统存储过程 sp_showrowreplicainfo (Transact-SQL) 查看此元数据。

合并代理在同步期间枚举要应用的更改时,会比较发布服务器和订阅服务器上每一行的元数据。 合并代理用此元数据确定行或列是否在拓扑中多个节点上进行了更改,而这将指示出潜在的冲突。 检测到冲突后,合并代理启动为发生冲突的项目所指定的冲突解决程序,并用此解决程序确定冲突解决入选方。 入选行将应用到发布服务器和订阅服务器,而落选行的数据则写入冲突表。

除非为项目选择了交互式冲突解决方案,否则冲突立即由合并代理自动解决。 有关详细信息,请参阅交互式冲突解决。 如果使用合并复制冲突查看器手动更改冲突的入选行,则合并代理在下一次同步期间将行的入选版本应用于落选服务器。

记录解决的冲突

合并代理根据冲突解决程序中的逻辑解决了冲突后,将根据冲突类型记录冲突数据:

  • 对于 UPDATE 和 INSERT 冲突,合并代理将行的落选版本写入项目的冲突表,其名称形式为:conflict_<发布名称>_<项目名称>。 常规冲突信息,如冲突类型,将写入到表 MSmerge_conflicts_info

  • 对于 DELETE 冲突,合并代理将行的落选版本写入到 MSmerge_conflicts_info 表。 当针对更新而删除落选时,因为没有落选行数据(因为它是删除),所以不向 conflict_<发布名称>_<项目名称> 写入任何内容。

根据为 sp_addmergepublication@conflict_logging 参数指定的值,在发布数据库、订阅数据库或两者(默认)中创建每个项目的冲突表。 每个冲突表的结构与它所基于项目的结构相同,只多出了 origin_datasource_id 列。 如果冲突表中数据的保留时间超过了发布的冲突保持期(使用 sp_addmergepublication@conflict_retention 参数指定,默认为 14 天),则合并代理将删除冲突表中的数据。

复制提供了复制冲突查看器和存储过程(sp_helpmergearticleconflictssp_helpmergeconflictrowssp_helpmergedeleteconflictrows)来查看冲突数据。 有关详细信息,请参阅如何查看和解决合并发布的数据冲突 (SQL Server Management Studio)如何查看合并发布的冲突信息(复制 Transact-SQL 编程)

影响冲突解决方案的因素

有两种因素影响合并代理如何解决它检测到的冲突:

  • 订阅类型:客户端或服务器(订阅是请求订阅还是推送订阅并不影响冲突解决方案)。

  • 使用的冲突跟踪类型:行级、列级或逻辑记录级。

订阅类型

创建订阅时,除了指定是推送订阅还是请求订阅之外,还要指定是客户端订阅还是服务器订阅;订阅在创建后,类型将无法更改(在 Microsoft SQL Server 的早期版本中,客户端订阅和服务器订阅分别称为“本地订阅”和“全局订阅”)。

分配了优先级值(从 0.00 至 99.99)的订阅称为服务器订阅;使用发布服务器的优先级值的订阅称为客户端订阅。 另外,具有服务器订阅的订阅服务器可以将数据重新发布到其他订阅服务器。 下表总结了每种订阅服务器类型的主要差别和用途。

类型

优先级值

使用

服务器

由用户指定

当希望不同的订阅服务器具有不同的优先级时使用。

客户端

0.00,但数据更改采用同步后发布服务器的优先级值

当希望所有订阅服务器具有相同优先级,并且希望与发布服务器合并的第一个订阅服务器在冲突中入选时使用。

如果行在客户端订阅中发生了更改,则直到订阅同步后优先级才分配给此更改。 同步期间将订阅服务器的更改分配给发布服务器的优先级,并且更改将在后续同步中保留此优先级。 从某种意义上,发布服务器采用更改的所有权。 此行为允许第一个订阅服务器与发布服务器同步,以便在与其他订阅服务器对给定行或列的后续冲突中入选。

当更改服务器订阅中的行时,更改的订阅优先级将存储在元数据中。 当更改的行与其他订阅服务器上的更改合并时,该订阅优先级将跟随此行。 这可以确保高优先级订阅所做的更改不会落选于低优先级订阅所做的后续更改。

订阅的显式优先级值不能比它的发布服务器高。 合并复制拓扑中的顶级发布服务器始终具有 100.00 的显式优先级值。 对该发布的所有订阅的优先级值必须小于此值。 在重新发布拓扑中:

  • 如果订阅服务器正在重新发布数据,则订阅必须是服务器订阅,且优先级值小于在订阅服务器之上的发布服务器的优先级值。

  • 如果订阅服务器没有重新发布数据(因为它位于重新发布树的叶级),则订阅必须是客户端订阅。

有关服务器订阅和优先级的详细信息,请参阅根据订阅类型和指定优先级的合并冲突解决示例

延迟冲突通知

具有不同冲突优先级的服务器订阅可能发生延迟冲突通知。 请考虑下面这种情况:非冲突更改在发布服务器和一个低优先级订阅服务器之间进行交换,而当一个高优先级订阅服务器与该发布服务器同步时,非冲突更改将成为冲突更改:

  1. 发布服务器和一个名为 LowPrioritySub 的低优先级订阅服务器通过几次同步交换更改,这时没有冲突。

  2. 一个名为 HighPrioritySub 的高优先级订阅服务器有一段时间没有和发布服务器同步,并更改了 LowPrioritySub 订阅服务器已更改的相同行。

  3. 因为 HighPrioritySub 订阅服务器的优先级比 LowPrioritySub 订阅服务器高,所以 HighPrioritySub 订阅服务器与发布服务器将同步并且在其更改与 LowPrioritySub 订阅服务器更改的冲突中会入选。 发布服务器现在包含 HighPrioritySub 订阅服务器所做的更改。

  4. 然后,因为 LowPrioritySub 订阅服务器和 HighPrioritySub 订阅服务器存在冲突,所以将与发布服务器合并,并下载大量更改。

当低优先级订阅服务器对在冲突中落选的相同行进行了更改时,这种情况可能会出现问题。 这可能导致丢失此订阅服务器所做的所有更改。 此问题的可能解决方案是确保所有订阅服务器具有相同的优先级,除非业务逻辑另外规定。

跟踪级别

数据更改是否符合冲突的条件取决于为项目设置的冲突跟踪类型:行级、列级或逻辑记录级。有关逻辑记录级跟踪的详细信息,请参阅检测并解决逻辑记录中的冲突

如果在行级识别冲突,则对相应行的更改将被判断为冲突,无论更改是否发生在同一列中。 例如,假定一个更改是对发布服务器行的地址列的更改,第二个更改是对相应订阅服务器行的电话号码列(在同一个表中)的更改。 使用行级跟踪将检测到冲突,因为对同一行进行了更改。 使用列级跟踪则不会检测到冲突,因为更改是在同一行中的不同列进行的。

对于行级和列级跟踪,冲突的解决方案是相同的:整行数据由冲突解决入选方的数据覆盖(对于逻辑记录级跟踪,解决方案取决于项目属性 logical_record_level_conflict_resolution)。

通常可以根据应用程序的语义决定采用哪个跟踪选项。 例如,如果正在更新通常同时输入的客户数据,如地址和电话号码,则应该选择行级跟踪。 如果在这种情况下选择了列级跟踪,则对一个位置的客户地址更改和另一个位置的客户电话号码更改不会检测为冲突:数据将在同步时合并,不会出现错误。在其他情况下,在不同站点更新单独的列可能是最合理的选择。 例如,两个站点可能会访问关于某一客户的不同类型的统计信息,如收入水平和信用卡的购买总金额。 选择列级跟踪可以确保两个站点都能在不同列输入统计数据,而不会生成不必要的冲突。

注意注意

如果应用程序不需要列级跟踪,则建议使用行级跟踪(默认值),因为它通常会产生更好的同步性能。 如果使用行跟踪,则基表最多可以包含 1,024 列,但必须从项目中对这些列进行筛选,因此最多可发布 246 列。 如果使用列跟踪,则基表最多可以包含 246 列。

冲突类型

虽然大多数冲突都与更新有关(一个节点的更新与另一个节点的更新或删除冲突),但仍存在其他冲突类型。 此部分中讨论的每种类型的冲突都可能在合并处理的上载阶段或下载阶段发生。 上载处理是在特定合并会话中执行的第一次更改调解,在此期间,合并代理将更改从订阅服务器复制到发布服务器。 在此处理期间检测到的冲突称为上载冲突。 下载处理包括将更改从发布服务器移动到订阅服务器,在上载处理后才发生。 在此处理阶段期间检测到的冲突称为下载冲突。

有关冲突类型的详细信息,请参阅 MSmerge_conflicts_info (Transact-SQL),尤其是 conflict_typereason_code 列。

更新-更新冲突

当一个节点上的行更新(或列更新或逻辑记录更新)与另一个节点上的同一行的另一个更新相冲突时,合并代理将检测到“更新-更新”冲突。 在这种情况下,默认解决程序的行为是将行的入选版本发送到落选的节点,并将落选的行版本记录到项目冲突表中。

更新-删除冲突

当一个节点上的数据更新与另一个节点上的删除冲突时,合并代理将检测到“更新-删除”冲突。 在这种情况下,合并代理将更新行;但当合并代理在目标位置搜索此行时将无法找到此行,因为此行已经被删除了。 如果入选方是对行进行更新的节点,则落选节点上的删除将被放弃,并且合并代理会将最近更新的行发送给冲突的落选方。 合并代理将有关落选版本的行的信息记录到 MSmerge_conflicts_info 表中。

失败更改冲突

当合并代理无法应用特定更改时,将引发这些冲突。 这通常是由于发布服务器和订阅服务器之间的约束定义存在差异并且在约束上使用了 NOT FOR REPLICATION (NFR) 属性引起的。 例如:

  • 订阅服务器上的外键冲突,这会在订阅服务器端约束没有标记为 NFR 时发生。

  • 发布服务器和订阅服务器之间的约束差异,并且约束没有标记为 NFR。

  • 订阅服务器上的相关对象不可用。 例如,如果发布了视图,但没有发布此视图所依赖的表,当尝试在订阅服务器上通过此视图进行插入操作时会发生失败。

  • 为主键和外键约束不匹配的发布联接筛选逻辑。 当 SQL Server 关系引擎尝试遵守某个约束,而合并代理正在遵守项目间的联接筛选器定义时,就会发生冲突。 合并代理由于表级约束而无法在目标节点上应用更改,这会导致冲突。

  • 如果为项目定义了标识列但没有使用自动标识管理,则会由于唯一索引或唯一约束冲突或主键冲突而导致冲突。 这在两个订阅服务器对最近插入的行使用相同的标识值时会出问题。 有关标识范围管理的详细信息,请参阅复制标识列

  • 由于触发器逻辑妨碍合并代理在目标表中插入行所导致的冲突。 考虑订阅服务器上定义的更新触发器;此触发器没有标记为 NFR 并且其逻辑中包含 ROLLBACK。 如果发生失败,此触发器发出 ROLLBACK 事务,这会导致合并代理检测到失败更改冲突。