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

合并复制允许多个节点进行自治数据更改,因此存在一个节点上所做的更改可能会与对另一个节点上相同数据所做的更改发生冲突的情况。 在其他情况下,合并代理遇到诸如约束冲突之类的错误,并且无法将特定节点上所做的更改传播到另一个节点。 本文介绍冲突类型、如何检测和解决冲突,以及影响检测和解决的因素。

检测和解决冲突

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

当合并代理枚举同步期间要应用的更改时,它会比较发布服务器和订阅服务器上的每一行的元数据。 合并代理使用此元数据来确定拓扑中的多个节点的行或列是否已更改,这表示潜在的冲突。 检测到冲突后,合并代理会启动为冲突项目指定的冲突解决程序,并使用冲突解决程序来确定冲突获胜者。 获胜行将应用于发布者和订阅者;而失败行的数据则写入冲突表。

除非为该项目选择了交互式冲突解决;否则合并代理会自动并立即解决冲突。 有关详细信息,请参阅 Microsoft复制交互式冲突解决程序。 如果你使用合并复制冲突查看器手动更改冲突的获胜行,则合并代理将在下次同步时将该行的获胜版本应用于失败的服务器。

记录已解决的冲突

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

  • 对于 UPDATEINSERT 冲突,它会将行的丢失版本写入项目冲突表,该表以格式 conflict_<PublicationName>_<ArticleName>命名。 常规冲突信息(如冲突类型)将写入表 MSmerge_conflicts_info

  • 对于 DELETE 冲突,它会将行 MSmerge_conflicts_info 的丢失版本写入表。 当删除因更新而丢失时,失败行没有数据(因为它是删除操作),因此不会向 conflict_<PublicationName>_<ArticleName> 写入任何内容。

每个项目的冲突表在发布数据库、订阅数据库或两者(默认值)中创建,具体取决于为 @conflict_logging 参数 sp_addmergepublication指定的值。 每个冲突表的结构与其所基于的文章相同,并添加了 origin_datasource_id 列。 合并代理会从冲突表中删除数据,如果这些数据早于发布中的冲突保留期(使用sp_addmergepublication@conflict_retention参数指定,默认值为14天)。

复制提供“复制冲突查看器”和存储过程(sp_helpmergearticleconflictssp_helpmergeconflictrowssp_helpmergedeleteconflictrows)来查看冲突数据。 有关更多信息,请参阅 合并复制中的冲突解决

影响冲突解决的因素

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

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

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

订阅类型

创建订阅时,除了指定它是推送订阅还是请求订阅之外,还指定它是客户端订阅还是服务器订阅;创建订阅后,无法更改类型(在以前版本的 SQL Server 中,客户端和服务器订阅分别被引用为本地订阅和全局订阅)。

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

类型 优先级值 已使用
服务器 由用户分配 如果希望不同的订阅者具有不同的优先级。
客户 0.00,但数据更改在同步后假定发布者的优先级值 如果希望所有订阅者具有相同的优先级,并且第一个订阅者与发布者合并以赢得冲突时。

如果在客户端订阅中更改了行,则在同步订阅之前,不会为更改分配任何优先级。 在同步过程中,来自订阅者的更改会被赋予发布者的优先级,并在随后的同步中保持该优先级。 从某种意义上说,发布者假定更改的所有权。 此行为允许第一个订阅者与发布者同步,以赢得与给定行或列的其他订阅者的后续冲突。

更改服务器订阅中的行时,订阅优先级存储在更改的元数据中。 当更改行与其他订阅者上的更改合并时,此优先级值将随更改行一起传播。 这可确保高优先级订阅所做的更改不会因较低优先级订阅所做的后续更改而丢失。

订阅的显式优先级值不能高于其发布者。 合并复制拓扑中的顶级发布者始终具有 100.00 的显式优先级值。 所有对该出版物的订阅必须拥有低于此值的优先级。 在重新发布的拓扑中:

  • 如果订阅者正在重新发布数据,则该订阅必须是一个服务器订阅,并且其优先级值低于位于该订阅者之上的发布服务器的优先级。

  • 如果订阅者未重新发布数据(因其位于重新发布树的叶级),则该订阅必须是客户端订阅。

有关服务器订阅和优先级的详细信息,请参阅 基于订阅类型和分配的优先级的合并冲突解决示例

延迟冲突通知

延迟冲突通知可能发生在具有不同冲突优先级的服务器订阅中。 请考虑以下方案:发布者和较低优先级的订阅者之间交换了不冲突的更改,当较高优先级的订阅者与发布者同步时,这些更改会导致冲突的更改:

  1. 发布者和一个名为 LowPrioritySub 的低优先级订阅者在多次同步过程中交换更改,而不会发生冲突。

  2. 名为 HighPrioritySub 的较高优先级订阅者有一段时间没有与发布者同步,并且对 LowPrioritySub 订阅者所做的相同行进行了更改。

  3. HighPrioritySub 订阅者与发布者同步,并赢得其更改与 LowPrioritySub 订阅者之间的冲突,因为它的优先级高于 LowPrioritySub 订阅者。 发布者现在包含 HighPrioritySub 订阅者所做的更改。

  4. 然后,LowPrioritySub 订阅者与发布者合并,并下载由于与 HighPrioritySub 订阅者冲突而产生的大量更改。

当较低优先级的订阅者对现在是冲突失败者的同一行进行了更改时,这种情况可能会变得有问题。 这可能会导致此订户所做的所有更改丢失。 此问题的潜在解决方法是确保所有订阅服务器具有相同优先级,除非业务逻辑另有规定。

跟踪级别

数据更改是否限定为冲突取决于为项目设置的冲突跟踪类型:行级别、列级或逻辑记录级别。 有关逻辑记录级别跟踪的详细信息,请参阅 高级合并复制冲突 - 在逻辑记录中解决

当在行级别识别到冲突时,无论是否对同一列进行更改,对相应行所做的更改都被判断为冲突。 例如,假设对发布服务器行的地址列进行了一项更改,对相应订阅服务器行的电话号码列(在同一表中)进行了第二次更改。 使用行级跟踪时,会检测到冲突,因为对同一行进行了更改。 使用列级跟踪时,不会检测到任何冲突,因为对同一行中的不同列进行了更改。

对于行级别和列级跟踪,冲突解决是相同的:整个数据行被冲突获胜者的数据覆盖(对于逻辑记录级跟踪,解决方法取决于项目属性 logical_record_level_conflict_resolution)。

应用程序语义通常确定要使用的跟踪选项。 例如,如果要更新通常同时输入的客户数据,例如地址和电话号码,则应选择行级跟踪。 在这种情况下,如果选择列级跟踪,则对一个位置的客户地址和另一个位置的客户电话号码的更改将不会被检测为冲突:数据将在同步时合并,从而导致错误被忽略。 在其他情况下,从不同站点更新各个列可能是最合乎逻辑的选择。 例如,两个网站可能有权访问客户不同类型的统计信息,例如收入水平和信用卡购买总额。 选择列级跟踪可确保这两个站点可以输入不同列的统计数据,而不会产生不必要的冲突。

注释

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

冲突类型

尽管大多数冲突都与更新相关(一个节点上的更新与其他节点的更新冲突或删除),但存在其他冲突类型。 本部分讨论的每种冲突都可以在上传阶段或合并处理的下载阶段发生。 上传处理是特定合并会话中执行的更改的第一次对帐,也是合并代理将更改从订阅服务器复制到发布服务器的阶段。 在此处理期间检测到的冲突称为上传冲突。 下载处理涉及将更改从发布服务器移动到订阅服务器,并在上传处理后发生。 此处理阶段的冲突称为下载冲突。

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

更新-更新冲突

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

更新与删除的冲突

当一个节点上的数据更新与另一个节点的删除冲突时,合并代理将检测更新-删除冲突。 在这种情况下,合并代理会更新行;但是,当合并代理在目标处搜索该行时,找不到该行,因为它已被删除。 如果获胜者是更新行的节点,则失败节点的删除操作会被丢弃,合并代理将新更新的行发送给冲突失败者。 合并代理将有关行丢失版本的信息记录到 MSmerge_conflicts_info 表中。

更改失败导致的冲突

合并代理在无法应用特定更改时引发这些冲突。 这通常是因为发布服务器和订阅服务器之间的约束定义存在差异,以及对约束使用 NOT FOR REPLICATION (NFR) 属性。 示例包括:

  • 订阅服务器上的外键冲突,当订阅服务器端约束未标记为 NFR 时,可能会出现此冲突。

  • 发布者和订阅者之间的约束存在差异,而且这些约束未标记为 NFR。

  • 订阅者上的依赖对象不可用。 例如,如果你发布了视图但未发布该视图所依赖的表,那么尝试通过订阅者上的该视图进行插入时将发生失败。

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

  • 如果为项目定义了标识列,并且未使用自动标识管理,则可能会因唯一索引或唯一约束违规或主键违规而发生冲突。 如果两个订阅服务器对新插入的行使用相同的标识值,则可能会出现此问题。 有关标识范围管理的详细信息,请参阅 “复制标识列”。

  • 由于触发逻辑阻止合并代理在目标表中插入行而导致的冲突。 考虑在订阅者上定义的更新触发器;触发器未标记为 NFR,其逻辑中包含 ROLLBACK。 如果发生故障,则触发器会发出事务的 ROLLBACK,这会导致合并代理检测到更改失败的冲突。