伯乐共勉

讨论。NET专区
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

缓慢分区合并进程的诊断与疑难解答

Posted on 2005-04-26 16:41  伯乐共勉  阅读(698)  评论(0)    收藏  举报
Dean Kalanquin,Merge Replication Test Lead
Microsoft Corporation
2002年1月

摘要:了解如何实现可扩展的高性能合并复制应用程序。

目录

简介

合并复制应用程序不但必须提供应用程序的业务规则所需的功能,而且即使数据量和用户数不断增加,也必须及时完成操作。

由于合并复制是通过 Microsoft® SQL Server™ 2000 中的表和查询实现的,因此其性能取决于 SQL Server 对复制处理需求进行处理的有效性,而该有效性又取决于数据库的设计及其性能优化的程度。

许多合并复制应用程序都能进行分区,分区使已复制数据的不同用户可以接收不同的数据集。合并复制中的性能问题通常是因分区的方式和 SQL Server 评估并处理与分区关联的表达式的有效性造成的。本文主要介绍了如何诊断和解决分区进程的性能问题,并介绍了一些影响性能的一般数据库设计原则。

分区与合并复制

在发布中处理分区定义,可能是造成合并复制中的性能问题的主要原因,但是对数据进行分区也可以增强应用程序的性能和灵活性,并有助于确保正确实现应用程序的业务规则。

设计合并复制的基本目的是以这样一种方式对数据进行分区,即每个订阅服务器接收最小数据集。在发布要复制的表时,请认真考虑是否每个订阅服务器都需要查看所有数据,或者您是否能够将数据的较小子集复制到每个订阅服务器。对数据进行分区非常有益,因为它可以:

  • 消除或减少对数据所做更改互相冲突的可能性。

    如果同一个数据集被多个数据库共享并更改,那么在一个数据库中所做的更改总有可能与在另一个数据库中所做的更改冲突。对数据所做更改的冲突增加了应用程序的复杂性,也增加了对复制的处理需求;因此,要尽可能地认真设计,以消除这些冲突。

  • 最小化每个订阅服务器所维护的数据量。

    较大的数据集将导致更多的处理开销。此外,订阅服务器硬件的性能通常低于发布服务器硬件的性能,而且不可能提供处理和存储整个表的副本所需的空间。另外,如果订阅服务器不具备高速连接,使用大数据集初始化订阅服务器可能是个很严重的问题。

  • 防止订阅服务器接收敏感数据。

    您可能希望阻止订阅服务器查看不合适的数据。发布服务器的完整数据集可以被限制为适合每个订阅服务器的数据集。

筛选器

在合并复制中,分区是通过两种不同的筛选器实现的:子集筛选子句和联接筛选器(也称为合并筛选器或跨表合并筛选器)。两种筛选器在发布中经常组合使用,使用子集筛选子句筛选各个表,而联接筛选器根据表之间的关系进行筛选。

两种筛选器都可以使用静态分区或动态分区。如果使用静态分区定义发布,则当合并进程运行时,所有订阅服务器都将收到相同的数据集。如果使用动态分区定义发布,则订阅服务器可以根据各自的连接属性(如登录),接收不同的数据集。

子集筛选子句

“子集筛选子句”是允许您定义逻辑表达式的项目属性。此逻辑表达式在用于复制表的查询的 WHERE 子句中使用。例如,发布服务器的 CUSTOMER 表包含多个州的客户数据,但是应用程序的业务规则要求发布将发布的数据限制为单个州。此表的子集筛选子句示例如下:

State = 'WA'

联接筛选器

外键关系是大多数数据库设计的重点。例如,在跟踪产品订单的数据库中,您经常会看到带有 CustID 主键的 CUSTOMER 表和带有 CustID 外键(引用 CUSTOMER 表)的 ORDER 表。

在合并复制中,如果使用子集筛选子句筛选 CUSTOMER 表,那么还必须筛选 ORDER 表,以便只复制那些引用 CUSTOMER 表中各行的已筛选子集的行。基于外键关系的筛选要求一定要通过“联接筛选器”(它列出了两个相关的表项目和用于标识相互关系的逻辑表达式),在合并复制配置中明确表示,如下例所示:

CUSTOMER.CustID = ORDER.CustID

CUSTOMER 表和 ORDER 表之间的联接筛选器与 CUSTOMER 表中的子集筛选子句一起发生作用,结果,发布的订阅服务器只接收 CUSTOMER 表中符合 State = 'WA' 的那些行,以及 ORDER 表中符合已筛选的 CUSTOMER 表中的 CustID 值的那些行。

基于外键关系的筛选和分区是限制数据的最常见方法,但有时,此方法并不适合您的实际情况。例如,订阅服务器可能需要接收所有 CUSTOMER 行,但只接收本州客户的 ORDER 行。CUSTOMER 表没有被筛选;因此,不能使用基于 CUSTOMER 表的联接筛选器筛选 ORDER 表。所有筛选都必须通过在 ORDER 项目的子集筛选子句中使用子查询来表示,例如:

ORDER.CustID IN 
(SELECT CustID FROM CUSTOMER WHERE State = 'WA')

虽然一些应用程序需要使用子查询,但是这一筛选技术可能不会像上一个示例中的联接筛选技术那样有效。建议您尽可能使用联接筛选器。

动态分区

在前面各节中讨论的筛选表达式是静态的,只生成一个分区数据子集。根据不同的州发布分区需要另一个发布,所以,如果应用程序要求每个州一个分区,则需要 50 个发布。因为管理和维护这么多发布需要很高的开销,所以合并复制支持动态分区,即在单个发布中发布不同的数据集。

如果使用动态分区,则子集筛选子句包含一个为每个分区返回不同值的函数。(各个订阅服务器可以拥有不同的分区,但不同的订阅服务器可能拥有相同的分区。)例如,CUSTOMER 表可能包含 SALESREP 列,该列包含每位客户的销售代表的网络用户帐户。那么,子集筛选子句将为:

SALESREP = SUSER_SNAME()

SUSER_SNAME() 是一个 Transact-SQL 函数,它为当前连接的用户返回登录。当订阅服务器连接到发布服务器以运行合并进程时,SUSER_SNAME() 函数将求值并与在动态筛选器中指定的列进行比较;与筛选器匹配的行被复制。例如,如果正在运行合并进程的销售代表连接到登录 domain\bobjones,那么分区将只包含那些符合 SALESREP = 'domain\bobjones'CUSTOMER 行。

Transact-SQL 函数 HOST_NAME() 也可以用于动态筛选器,以便根据订阅服务器的标识,而不是根据订阅服务器登录筛选。SUSER_SNAME() 对应于合并代理程序参数 -PublisherLogin,而该参数又对应于 SQL 事件探查器的列名 LoginName。同样,HOST_NAME() 对应于合并代理程序参数 -Hostname 和 SQL 事件探查器的列名 HostName。SQL 事件探查器的列名将在下面的 SQL 事件探查器数据列一节中讨论。有关详细信息,请参阅 SQL Server 联机丛书中的“复制合并代理程序实用工具”和“SQL 事件探查器数据列”主题。

动态筛选也需要初始化新的订阅服务器,请参阅 sp_Msinitdynamicsubscriber 和本文下面的使用动态快照一节。

筛选器的内部实现

当您在合并复制发布中定义分区时,合并复制在基于筛选表达式的已发布数据库中创建视图,然后发布这些视图所代表的数据子集。

例如,使用 State = 'WA' 的子集筛选子句为 CUSTOMER 项目创建的视图是:

CREATE VIEW [publication_CUSTOMER_VIEW] AS  
SELECT * FROM [dbo].[CUSTOMER] [CUSTOMER] 
WHERE  (State = 'WA')

使用 CUSTOMER 表为具有联接筛选器的 ORDER 项目创建的视图是:

CREATE VIEW [publication_ORDER_VIEW] AS 
SELECT [ORDER].*  
FROM [dbo].[ ORDER] [ORDER], [publication_CUSTOMER_VIEW] [CUSTOMER] 
WHERE CUSTOMER.CustID = ORDER.CustID

这些分区视图以及它们所包含的筛选表达式对复制分区数据的性能十分重要。它们定义必须由 SQL Server 执行的操作。索引将确定 SQL Server 如何执行操作。如果视图过于复杂,或者不能在基准表中使用索引,则整组相关的已发布表的性能将会下降。

联接筛选器和 @join_unique_key

sp_addmergefilter(存储过程,用于添加联接筛选器)的 @join_unique_key 属性影响 SQL Server 如何建立联接筛选器视图。当联接逻辑只基于筛选条件表中的唯一逻辑时,将它设置为 1。否则,将它设置为 0

例如,本文先前介绍的联接筛选器,表示基于 CUSTOMER.CustID = ORDER.CustID 条件,ORDER 表和 CUSTOMER 表之间的联接。如果 CUSTOMER 表(筛选条件表)中的 CustID 字段具有唯一的索引,则比较基于唯一列(或带有唯一索引的一组列),且 @join_unique_key 的值应为 1。结果,产生一个执行 ORDER 表和 CUSTOMER 视图之间的联接的分区视图:

CREATE VIEW [sfa_publ_ORDER_art_VIEW] AS 
SELECT [ORDER].* 
FROM [dbo].[ORDER] [ORDER] , [sfa_publ_CUSTOMER_art_VIEW] [CUSTOMER] 
WHERE (CUSTOMER.CustID = ORDER.CustID)

然而,如果本例中的 CUSTOMER.CustID 值不唯一,那么此联接技术对分区进程无效。ORDER 行将被错误地复制,以匹配 CUSTOMER 表中已复制的 CustID 值。在这种情况下,筛选器的 @join_unique_key 设置应被设置为 0,这导致在 ORDER 视图中进行再选择而不是联接,如下所示:

CREATE VIEW [sfa_publ_ORDER_art_VIEW] as 
SELECT *  FROM [dbo].[ORDER] ORDER_alias_1
WHERE rowguidcol IN 
(
SELECT [ORDER].rowguidcol 
FROM [dbo].[ORDER] [ORDER_alias_2] , [sfa_publ_CUSTOMER_art_VIEW] [CUSTOMER] 
WHERE ( CUSTOMER.CustID = ORDER_alias_2.CustID)
)

此示例相对比较简单,因为它反映那些要求 CustID 列唯一的表之间的主键与外键的关系。在更复杂的方案中,可能很难确定逻辑是否唯一。如果联接筛选表达式包含由 OR 运算符组合的多个表达式,或者包含通配符或 NOT 运算符,则在将 @join_unique_key 属性设置为 1 时要格外小心。

@join_unique_key 属性是可用的,因为在视图中使用联接允许 SQL Server 查询分析器使用更有效的技术处理查询并返回结果集。如果已经将该属性设置为 0,并且联接逻辑确实唯一,那么您并没有充分利用可用的最佳处理技术。另一方面,如果将该属性设置为 1,而且联接逻辑不唯一,则当数据应用于订阅服务器时,联接技术返回重复数据并且结果为主键冲突。

分区及其对性能的影响

需要注意的是,当上一节中的视图表示联接筛选器时,这些视图是“嵌套”的。ORDER 视图基于与 CUSTOMER 视图的联接。如果将 ORDER_ITEM 项目添加到此发布,并且在 ORDER 项目基础上筛选 ORDER_ITEM 项目,那么 ORDER_ITEM 的分区视图将包含与 ORDER 分区视图的联接,如下所示:

CREATE VIEW [publication ORDER_ITEM_VIEW] AS 
SELECT [ORDER_ITEM].*  
FROM [dbo].[ ORDER_ITEM] [ORDER_ITEM], [publication_ORDER_VIEW] [ORDER] 
WHERE ORDER_ITEM.OrderID = ORDER.OrderID

随着包含的关系层次的增多,视图定义逐渐变得复杂。这可能是造成性能问题的主要原因,因为当较低层次的查询变得更复杂时,较高层次(例如,在 CUSTOMER 表中)中的问题就会放大。

如果在发布中定义分区,则合并进程必须确保每个订阅服务器为其分区接收相应数据集。在初始化新的订阅服务器时,这相对简单,因为合并进程从发布服务器的视图中选择相应的数据,并使用结果数据集填充订阅服务器。但是,当对发布服务器或订阅服务器的数据进行更改,以及在分区中添加或删除行时,合并进程必须通过添加或删除相应的行来更新订阅服务器的数据集。

例如,如果使用逻辑 State = 'WA' 筛选 CUSTOMER 项目,并且其中一位客户重定位到 Oregon,则 State 列被相应地更新。包含此客户的行将不再归入 State = 'WA' 分区,所以,合并进程必须从所有应该只包含 State 列的值为 WA 的数据的订阅服务器中删除它。ORDER 数据基于 CUSTOMER 表,而 ORDER_ITEM 数据基于 ORDER 表。因此,从 State='WA' 分区中删除客户,意味着合并进程还必须在属于此客户的 ORDERORDER_ITEM 表中删除关联的订阅服务器行。相反,在 ORDERORDER_ITEM 表中的客户行及其相关行必须被添加到接收 State = 'OR' 分区(表示 Oregon)的订阅服务器的数据集中。

合并复制进程可能需要大量 SQL Server 处理资源填充和维护已分区的数据集,特别是在发布更多数据集的时候。嵌套逻辑变得更加复杂,而且已发布的表中的数据量增加。因此,与分区性能相关的问题通常与层次结构中较低层次中的较大表关联。

在处理分区视图时,合并复制分区处理的有效性取决于 SQL Server 使用高效查询优化技术的能力。您所使用的筛选表达式的类型以及支持筛选表达式的索引的存在,又决定了 SQL Server 可以使用哪些查询优化技术。

索引分区与合并分区的性能

索引可以大大缩短处理时间,并减少那些允许 SQL Server 在 WHERE 子句中对列使用索引的查询所需的处理资源(内存、锁定等等)。

当合并复制处理某个已分区的数据集时,它根据上一节介绍的分区视图来执行查询。分区的数据集的快速返回主要取决于使用索引进行查询的能力。如果数据库不能有效使用索引,或者根本不提供索引,则分区处理性能会受到影响而且复制进程可能会失败。

请使用用于高效查询的筛选条件来定义分区,并尽可能地使用索引。有关有效索引设计的详细信息,请参阅 SQL Server 联机丛书中的“索引, 设计”主题。

注意:尽管索引能够有效地改善查询性能,但由于 SQL Server 必须维护索引,所以使用索引会增加系统开销。只要用户添加行、删除行或者更新索引列,SQL Server 就必须更新索引。只有当索引的好处(对于合并进程和其他查询也是一样)大于开销时,才使用索引。使用索引优化向导有助于确定哪些索引比较合适。

索引维护

随着不断地插入、更新以及删除行,索引可能会变得零碎。因此,有必要不定期地整理碎片或重新生成索引,以便有效而紧凑地存储它们。整理索引碎片或重新生成索引对于用户表和合并复制系统表很重要。例如,只要在任何已发布的表中插入、更新或删除行,就会更改 MSmerge_contents 系统表。当表接收大量活动时,它的索引会变得零碎,导致合并复制进程变慢。

有多种方法可以整理索引碎片或重新生成索引。要整理索引碎片,请使用 DBCC INDEXDEFRAG。要重新生成索引,请先删除然后重新创建索引,或者使用 DBCC DBREINDEX()。如果索引与 PRIMARY KEY 或 UNIQUE 约束条件关联,则可能无法删除并重新创建索引。在这种情况下,应使用 DBCC DBREINDEX()。有关详细信息,请参阅 SQL Server 联机丛书中的“索引, 碎片整理”和“索引, 重建”主题。

由不良索引分区造成的问题

糟糕的数据库设计和不理想的分区实现可能会造成多种不同的问题:

  • 长时间运行合并进程
  • 超时
  • 锁定、阻塞和死锁

在第一次尝试复制数据时可能会出现这些问题,或者随着数据量及用户数目的逐渐增长,才出现这些问题。

长时间运行合并进程

与合并进程相关的最常见问题之一是执行进程所需时间太长。如果存在分区,该问题可能是不理想的优化分区逻辑造成的。

超时

合并代理程序(Replmerg.exe 或合并 ActiveX® 控件)允许您为合并进程指定 -QueryTimeout 值。如果查询不能在由 -QueryTimeout 值指定的秒数内完成,则进程失败,并显示以下错误消息:“发生超时。”

这并不是一个问题或缺陷;即使是在最佳环境下查询最佳优化分区,仍可能会超出指定的秒数才能完成操作。增加合并代理程序的 -QueryTimeout 值也许是唯一的解决方案。不过,如果出现超时,说明您应该重新评估分区逻辑并对其进行优化(如果可能)。

锁定、阻塞和死锁

当 SQL Server 访问表中的数据时,它将在进程期间锁定数据,以确保进程期间数据的事务一致性。在此期间,其他进程被锁定,而且必须等到解除锁定之后,它们才可以继续进行。如果没有优化查询,将有更多的数据被锁定,而且锁定时间会更长。这意味着其他进程被锁定的时间更长。

主要在其他进程与合并进程同时访问数据时,合并复制才存在阻塞问题。糟糕的优化分区查询可能保持锁定并阻塞其他进程,或者其他进程可能保持那些阻塞合并进程的锁定。被阻塞的进程可能会出现错误,这取决于数据库的设置(即 SET LOCKTIMEOUT)。

死锁问题与锁定问题的表现略有不同。在本质上,二者都是指一个进程或连接持有某个资源(表、索引或行)并等待另一个资源。第二个进程或连接持有第二个资源并等待第一个资源。除非其他进程完成并释放资源,否则任何进程都不能完成。SQL Server 识别这种情况,使其中一个进程失败并显示错误消息:“发生死锁,该进程已被选作死锁牺牲品。”

如果合并代理程序失败并出现此错误,则它通常是由临时资源问题造成的。当其他进程已完成操作并释放出资源时,合并进程很可能在下次执行时成功。因此,合并进程会因“可重试”错误而失败;如果使用 SQL Server 代理自动处理,则该作业会自动再次启动。

如果分区不是最佳的,则合并复制将需要大量锁定并将持续更长时间,而且合并复制进程会更容易出现死锁失败。此外,这还可能是实现合并复制的自然副作用,但只要使用有效分区就可以减少发生死锁的可能。

诊断合并分区的性能问题

如果您遇到合并进程运行时间过长,或者合并进程由于超时或锁定和阻塞问题而失败,请评估分区逻辑以确保您尽可能使用有效的技术。要诊断性能问题,您可以:

  • 评估分区定义。
  • 分析代理程序超时失败。
  • 分析缓慢合并进程中的步骤以查找非最佳查询处理。

评估分区定义

研究性能问题最简单的方法是分析分区定义以确保:

  • 子集筛选子句和联接筛选器中的筛选表达式使用“索引友好”表达式。
  • 表达式中使用的列已经编制索引。
  • 正确使用 @join_unique_key 设置。

您应该在初始设计和创建数据库和发布时进行这种分析。在生产中应用此应用程序时,后续检查也同样大有益处,因为有若干种因素(如数据容量、用户数量和数据更改模式等)可能改变应用程序的寿命,这是在项目的设计和实现阶段无法预见的。

评估分区定义的最佳方式是检查由合并复制基于子集筛选子句和联接筛选表达式创建的分区视图。分区视图以 publicationname_articlename_VIEW 的形式命名。要检查视图定义,请使用以下方法之一:

  • 在 SQL Server 企业管理器中,右键单击视图名称,然后选择 Properties(属性)。
  • 在 SQL 查询分析器中,运行:
    EXEC sp_helptext publicationname_articlename_VIEW
    

另一种方法是检查发布中定义的项目定义和筛选定义。可以使用以下任意一种方法:

  • 在 SQL Server 企业管理器中,右键单击发布名称,选择 Properties(属性),然后选择 Filter Rows(筛选行)选项卡。
  • 在 SQL Server 企业管理器中,右键单击发布名称并选择 Generate SQL Script...(生成 SQL 脚本...)。生成的 Transact-SQL 脚本包括包含分区表达式的 sp_addmergearticlesp_addmergefilter 调用。

查看分区视图时,请确保所有 WHERE 子句表达式都使用了索引。

要检查索引定义,请执行以下操作之一:

  • 使用 SQL Server 企业管理器。
  • 检查 Transact-SQL 脚本(无论是手动创建还是通过 SQL Server 企业管理器的脚本功能创建)。
  • 查询数据库中的系统表信息。

尽管检查视图定义和索引可能无法明确标识合并进程的哪一部分导致了性能问题,但它确实提供了一种标识明显问题的快速方法,从而使性能得以显著改善。

分析代理程序超时失败

数据库和应用程序投入实际使用一段时间后,数据量和用户数量可能会增长,这将导致合并进程逐渐变慢。最终,合并进程中的某个步骤可能由于超时错误而失败。如果合并进程由于错误而失败,请检查 SQL Server 企业管理器中的错误详细信息:

  1. 选择 Replication Monitor(复制监视器),再选择 Agents(代理程序),然后选择 Merge Agents(合并代理程序)。
  2. 右键单击有错误的合并代理程序,然后选择 Error Details...(错误详细信息...)。

如果超时与分区合并性能有关,则正在执行的命令可能是 sp_MSinitdynamicsubscribersp_MSsetupbelongs。从这一点来看,诊断步骤与下一节中所述大体相同。

分析缓慢合并进程中的步骤

分析合并性能问题的最彻底方法是浏览整个合并进程,标识执行缓慢的步骤,并分析 SQL Server 执行计划以标识是否使用了索引。

合并进程启动时,将至少打开三个连接:连接到发布服务器、分发服务器和订阅服务器。如果合并进程打开多个到发布服务器或到订阅服务器的线程以加速并行处理会带来益处,那么它将打开更多的连接。

在初始化进程中打开连接后,更改将从订阅服务器上载到发布服务器。然后更改从发布服务器下载到订阅服务器。当第一次与新订阅服务器一起运行合并进程时,不需要上载订阅服务器更改。因此,此进程包括将初始数据集(快照)应用到订阅服务器,然后从发布服务器下载创建快照之后发生的任何更改。

本节中执行的分析从定义 SQL 事件探查器跟踪开始。它只涉及下载步骤中与发布服务器的连接,因为正是在这里执行分区查询。

定义 SQL 事件探查器跟踪

您可以使用 SQL 事件探查器实用程序显示由客户端发送到 SQL Server 实例的查询并提供关于每个已提交查询的信息。SQL 事件探查器可用于获取许多进程的信息,但本节仅提供有关诊断合并进程的特定信息。有关 SQL 事件探查器的详细信息,请参阅 SQL Server 联机丛书。

SQL 事件探查器使用跟踪定义,此定义是一个设置集合,描述了配置文件中包括的操作以及为每个操作记录的信息。除了正在分析的服务器的连接信息,跟踪定义还包含三种类型的设置:事件、数据列和筛选器。

跟踪可能返回成千上万行外来信息;因此,必须限制针对合并进程生成的信息所返回的信息行。使其仅包括对分析有用的列。如果合并进程是当前唯一在服务器上运行的进程,最简单的方法是限制 SQL 事件探查器返回的行,使其仅与合并进程相关。这在生产环境中通常是不可能的,因此可能需要使用以下各节列出的设置限制跟踪中返回的信息的数量和类型。

SQL 事件探查器事件

SQL 事件探查器事件包括由用户或 SQL Server 执行的活动(例如连接或断开连接、执行查询等)。必须明确标识跟踪中包含的事件。对于分析合并复制进程来说,最重要的事件是 Stored Procedures, RPC: Completed,因为这就是合并复制在 SQL Server 中提交查询并执行操作的方式。所有其他事件都应从跟踪中删除,除非有特殊要求。

SQL 事件探查器数据列

数据列定义了为每个事件记录的信息的类型。对于分析合并复制进程,最重要的列包括:

  • TextData,说明合并进程步骤中执行的查询或过程调用。
  • Duration,说明 SQL Server 执行此步骤所用的时间。

其他有用的列包括:

  • DatabaseIdSpid,当发布服务器和分发服务器位于同一服务器上时(此情况在合并复制中很常见),使您可以区分发布服务器连接和分发服务器连接。
  • LoginNameHostName,当同时运行多个合并进程时,使您可以区分不同订阅服务器的合并进程。

SQL 事件探查器筛选器

SQL 事件探查器筛选器使您可以明确地排除或包括特定事件,与仅使用跟踪定义相比,它更灵活更精细。这仅在需要执行许多其他操作的大流量服务器上才是必需的。有关使用 SQL 事件探查器筛选器的详细信息,请参阅 SQL Server 联机丛书。

跟踪合并进程

定义 SQL 事件探查器跟踪后,请先启动它,然后启动合并进程。对于合并代理程序作业,可以使用 Start(启动)选项通过 SQL Server 企业管理器手动启动合并进程。但是,SQL 事件探查器也在跟踪中包括来自 SQL Server 企业管理器操作的外来信息。我们建议您使用排除应用程序 MS SQL EM 的跟踪筛选器或者从命令行启动进程。要从命令行启动合并进程,请从 SQL Server 企业管理器的合并代理程序作业中复制命令行参数:

  1. 选择 Replication Monitor(复制监视器),然后选择 Merge Agents(合并代理程序)。
  2. 选择要分析的合并代理程序。
  3. 右键单击此代理程序,选择 Agent Properties(代理程序属性),然后选择 Steps(步骤)。
  4. 选择 Run Agent(运行代理程序)步骤并选择 Edit(编辑)。
  5. Command(命令)框中,复制类似如下所示的文本:

    -Publisher ServerName -PublisherDB PublisherDBName -Publication PublicationName -Subscriber [SubscriberName] -SubscriberDB [SubscriberDBName] -Distributor [DistributorName] -DistributorSecurityMode 1

复制参数后,使用代理程序作业的参数从命令行执行 Replmerg.exe。(如果包括参数 -Continuous,进行此分析时请排除此参数。)如果接收到以下错误消息,请使用显式路径:

“replmerg.exe”不是内部或外部命令,
也不是可运行的程序或批处理文件

对于默认的 SQL Server 2000 安装,此路径是 %systemdrive%\Program Files\Microsoft SQL Server\80\COM\replmerg.exe。

启动合并进程后,您将发现命令在执行时被添加到跟踪窗口。等待合并进程结束,然后停止 SQL 事件探查器跟踪。此时,SQL 事件探查器屏幕显示包含有关所执行活动的信息行的网格。

注意:如果您是使用测试系统执行此分析,模拟实际的合并进程是很重要的。合并进程应当有要复制的更改,并且复制的更改应当可以代表生产环境中遇到的更改类型。

标识步骤中正在执行的操作

合并进程和 SQL 事件探查器跟踪完成后,标识持续时间最长的跟踪步骤。您可以直接使用 SQL 事件探查器屏幕中的信息,但将跟踪保存为一个表可能更容易些,尤其是当跟踪返回大量的行时。要将跟踪保存为表,请依次单击 File(文件)> Save As(另存为)> Trace Table(跟踪表),然后选择 SQL Server Table(SQL Server 表)。保存信息后,可以查询保存的表,以便按持续时间对数据排序,然后使用 WHERE 子句基于 DatabaseIdSpidLoginNameHostName 等除去外来行。

一旦查找到运行时间长的步骤,就可以从跟踪数据的 TextData 列中检索已执行命令的文本。

分区处理中的通用步骤

分区数据处理中使用的两个复制存储过程调用是 sp_MSsetupbelongssp_MSinitdynamicsubscriber。如果运行时间最长的步骤中包括其中任一个过程调用,很可能分区处理就是导致性能问题的原因。确定过程调用正在处理的项目,然后确定导致步骤需要长时间才能完成的子集筛选子句或联接筛选器。

sp_MSsetupbelongs

sp_MSsetupbelongs 过程确定每个表中哪些行要复制到订阅服务器。它通过查询每个经过筛选的表的分区视图执行此操作。如果分区视图所包含的查询优化不佳,sp_MSsetupbelongs 将花费较长的执行时间。

sp_MSsetupbelongs 步骤与特定项目(和分区表达式)相关联可能很困难,因为此过程与一组包含了来自若干不同项目的更改的“代”(即一批更改的版本号)一起调用。使情况更加复杂的是,sp_MSsetupbelongs 过程使用若干临时表,而这些表在合并进程完成后将不存在。

因此,您可能需要试用不同跟踪事件,例如:

  • TSQL: Stmt StartingTSQL: Stmt Completed
  • Scan:StartedScan:Completed
  • Performance:ShowPlanStatisticsPerformance:ShowPlanAllPerformance:ShowPlanText

在跟踪中包含这些事件时一定要注意,因为即使是相对简单的合并进程也能为这些事件生成数百万行跟踪信息。您也许可以筛选输出,但即使能够这样做,要成功使用这些事件仍然很困难。

修改 sp_MSsetupbelongs 以记录诊断信息

由于很难确定 sp_Mssetupbelongs 进程期间哪些项目导致了性能问题,因此有必要通过添加一个步骤(用于记录有关处理每个项目所用时间的诊断信息)来手动修改 sp_Mssetupbelongs

这些建议均出于诊断目的,仅可用于模拟生产进程的测试系统。下一节的开始部分将介绍恢复这些更改的指令。此代码示例适用于 SQL Server 2000,但 view_sel_proc 的使用与在 SQL Server 7 中基本相同。

修改 sp_MSsetupbelongs 前,请在发布数据库(将在执行 sp_Mssetupbelongs 时载入数据)中创建一个表。

CREATE TABLE article_diagnostics
(
view_sel_proc sysname, 
start_stamp datetime default getdate()
)

sp_MSsetupbelongs 有两个主要阶段:

  1. 创建临时表并将需要复制的关于更改的信息填入表中。
  2. 通过查询与发布表联接的临时表获取基于项目的更改。

修改 sp_MSsetupbelongs 后,它将填写您创建的 article_diagnostics 表,其名称为第二步中处理的项目的名称。

要修改 sp_MSsetupbelongs,必须首先在 SQL Server 安装程序的 INSTALL 目录中找到 Replmerg.sql 文件,其默认位置为:

  • %systemdrive%\Program Files\Microsoft SQL Server\MSSQL\Install(默认实例),或
  • %systemdrive%\Program Files\Microsoft SQL Server\MSSQL$InstanceName\Install(命名实例)

复制此文件并以新名称命名(例如 Newreplmerg.sql),然后使用编辑器打开它。找到循环整个项目的部分,此部分的开始为:

   while (@artnick is not null)
   begin
      select   @artbaseobjid = objid, @procname = view_sel_proc, 
            @before_view_objid = before_view_objid,
            @before_table_objid = before_image_objid 
            from sysmergearticles 
            where pubid = @pubid 
            and nickname = @artnick

在 SELECT 语句后,粘贴命令:

INSERT INTO article_diagnostics (view_sel_proc) 
VALUES (@procname)

view_sel_proc 过程存储在 article_diagnostics 表中,可用于以后查找分区视图名称。在每一行添加到此表的同时将载入 start_stamp 列,使您可以确定从一个 view_sel_proc 执行到下一个执行的时间。

插入此命令后,保存文件(使用其新名称)并通过使用 osql 实用程序在命令行执行脚本来安装修改的过程:

OSQL -Usa -P -S -n -inewreplmerge.sql

(您可能需要适当调整此命令行以保证 SQL Server 安装的安全设置。)

确定分区

创建表并修改 sp_MSsetupbelongs 后,照常重新执行合并进程(不必运行 SQL 事件探查器),执行结束后检查 article_diagnostics 表以查看 view_sel_proc 名称和处理每个分区所用的时间。

执行分析时,必须在合并代理程序的运行之间通过执行插入、更新和删除来模拟实际数据流。如果没有要处理的分区更改,则仍然调用 sp_MSsetupbelongs,但不为取决于分区的项目浏览视图选择过程。

注意:分析完成后,必须通过重新安装原来的 Replmerg.sql 文件来重新安装过程的原始版本,方法是:
   OSQL -Usa -P -S -n -ireplmerg.sql

sp_MSinitdynamicsubscriber

当合并进程第一次与新订阅服务器一起运行时,将执行初始合并。初始合并的作用是为该订阅服务器填入完整的数据集。非动态发布的订阅服务器通过使用大容量复制程序 (bcp) 操作(在快照处理过程中创建的文件中复制数据)进行初始化。这种技术速度非常快,并且很少会导致性能问题;需要优化的选项也较少。

另一方面,对于订阅动态筛选发布的订阅服务器来说,快照处理不能为其确定合适的数据集(除非使用动态快照)。因此,合并进程必须使用 sp_MSinitdynamicsubscriber 通过查询已发布的表来填写数据集。这种技术比使用 bcp 文件初始化订阅服务器的效率要低很多,并且通常会导致分区性能问题。

如果跟踪信息指示某个 sp_MSinitdynamicSubscriber 调用所用时间相对较长,您可以将此项目的别名视为已执行过程的第二个参数。此项目别名可用于获取与运行时间较长的步骤相关联的分区视图名称:

SELECT view_sel_proc
FROM sysmergearticles
WHERE nickname = 123456

获取分区视图定义

与运行时间较长的查询相关联的 view_sel_proc 过程用于查询分区视图并获取分区数据集。执行 sp_helptext 以查看 view_sel_proc 定义:

EXEC sp_helptext view_sel_proc

view_sel_proc 定义的开始是若干初始化步骤,然后是两个引用分区视图的查询,如下例所示:

select @tablenick, v.[rowguid], coalesce (c.generation,1), 
coalesce (c.lineage, @lin), coalesce (c.colv1, @cv), v.* from 
   [dbo].[CustPub_Customer_VIEW] v left outer join  
      dbo.MSmerge_contents c on  v.[rowguid] = c.rowguid  and 
         c.tablenick = @tablenick where v.[rowguid] > @guidlast 
 order by v.[rowguid]
insert into #belong (tablenick, rowguid, flag, skipexpand, partchangegen, 
   joinchangegen)
select ct.tablenick, ct.rowguid, 0, 0, ct.partchangegen, ct.joinchangegen 
   from #contents_subset ct, [dbo].[CustPub_Customer_VIEW] v where 
      ct.tablenick = @tablenick and ct.rowguid = v.[rowguid]  

这两个查询的 FROM 子句包含分区视图名称;在本例中,视图名称是 CustPub_Customer_view

收集分区视图查询计划

当已知导致性能问题的分区视图定义时,您的目标可分为两部分:

  • 了解视图及其如何表示筛选逻辑的内部实现。
  • 确定 SQL Server 如何处理此视图以及视图的哪些方面导致它运行缓慢。

要从视图定义中提取查询,请使用 sp_helptext,如下所示:

EXEC sp_helptext CustPub_Customer_view

得到分区视图的文本后,检查视图的 Transact-SQL 定义。您或许可以通过要求在发布中更改筛选表达式的视图来标识问题。

要获取有关 SQL Server 如何处理此视图的信息,必须使用特定设置从视图定义执行查询,以使 SQL Server 显示查询计划。查询计划说明了 SQL Server 如何将查询分为单个步骤、每个步骤所占的时间百分比以及执行每个步骤所使用的技术。

要查看查询计划,请从视图中复制 SELECT 语句并将其粘贴到 SQL 查询分析器的查询窗口中。在 SQL 查询分析器中,选择 Query(查询),选择 Show Execution Plan(显示执行计划),然后执行查询。查询结果窗口现在将包含 Execution Plan(执行计划)选项卡,用于显示查询计划的图形表示。

执行视图的查询可以给出合并进程期间执行的查询的合理模拟,但是,执行视图的查询并不包含与 MSmerge_contents#contents 表的联接(您可在上一个代码示例中看到这样的联接)。要使查询模拟更真实,可以在过程中添加更多的诊断步骤,以便将 MSmerge_contents#contents 数据的副本保存到另一个表中,供以后导出查询计划时使用。

如果分区包含动态表达式,则必须确保分析查询所使用的值与合并进程使用的动态表达式的值相同。否则,分析的分区数据集可能会与合并进程明显不同。例如,考虑以下筛选表达式:

SALES_REPL = SUSER_SNAME()

合并进程可能在“domain\bobjones”帐户下运行,并且您可能以系统管理员身份登录执行分析。获取查询计划时,可以使用与合并进程相同的帐户登录,也可以从视图中手动提取 SELECT 语句并用硬编码文字值“domain\bobjones”替换 SUSER_SNAME() 值。

分析查询计划

尽管 SQL Server 使用多种复杂技术处理查询的步骤,但基本上是用以下两种方法之一来处理查询中的行:通过浏览表或聚集索引中的每一行来执行“扫描”,或者通过定位至索引的特定位置执行“索引搜索”。与索引搜索相比,扫描需要更多的系统开销和处理时间,并导致更多的性能问题。

SQL Server 仅在由于以下原因之一不能使用索引时才执行扫描:索引不存在;WHERE 子句不能使用存在的索引;或者索引列中的数据并不完全唯一。

当分区视图查询计划中最长的步骤显示为使用扫描时,则没有使用索引。计划步骤的详细信息通常说明了扫描行时 SQL Server 正在使用的 WHERE 子句表达式。此表达式也可用于查询优化。

如果使用前面所列的分区示例,此表达式应该为:

WHERE State = 'WA'

找到创建非最佳计划的查询和表达式之后,可以使用若干选项来改进查询计划,这将在下一节中介绍。

通过改善查询计划和优化代理程序属性来改善性能

在采用特定方法改善分区性能之前,有必要重申在本文简介中提到的要点:复制性能取决于 SQL Server 的性能,而 SQL Server 的性能则取决于有效的数据库设计。

数据库设计包括数据库中存在的表定义和索引,以及它们如何有效地支持对应用程序的业务规则和复制进程的需要。合并复制的性能问题的最常见、也是最严重的原因之一是数据库设计不完善:要么其基础设计有缺陷,要么没有成功满足复制进程的需要。

即使是并未安装复制,而且已经完善地设计和优化的数据库,如果您随后安装并使用复本,也会遇到性能问题。可以发布一个小型的简单数据库,无需过多考虑性能问题并使其无限期地成功运行。但如果发布大型的复杂数据库,其大小和范围不断增长,那么复制性能的需要常常会决定应用程序成功与否。在一开始设计新的应用程序时就应认真考虑复制需求。向现有数据库添加复本时,可能需要更改数据库本身,以适应复制需求。

添加索引以支持筛选表达式

改善分区性能的最简单的解决方案就是添加索引(如果索引不存在)。在前面的示例中 WHERE State = 'WA' 表达式使用了表扫描。如果此列没有索引,则应考虑添加一个。如果分区表达式在 WHERE 子句中包含多个条件,则可能需要添加包含查询的复合索引。请考虑带有如下表达式的示例:

WHERE State = 'WA' AND
      City = 'Redmond' AND 
      Postal_Code = 98056

在这三个列中添加索引,可以更好地包含查询,而且对于查询,此索引更有用。例如:

CREATE INDEX cust_loc_index ON CUSTOMER (Postal_Code, City, State)

最好先将最有可能选择的列在复合索引中列出。SQL Server 索引优化向导可能会提供有关索引更改的具体建议,这些更改将改善查询的性能。

更改筛选表达式

如果筛选表达式包含复杂表达式(带有函数、多个运算符,等等),SQL Server 则可能无法有效地使用索引进行查询。应该评估该表达式,确定是否可以简化或更改它,以便使用索引更有效地进行工作。

使用查询提示

对高级查询优化的大多数讨论都包括对查询提示的引用,查询提示可以用于替代标准查询优化,并强制 SQL Server 使用您指定的方法。SQL Server 通常选择最佳的查询执行计划,并且可以调整执行计划以适应可能影响最佳查询计划的数据更改。所以,在尝试改进查询的过程中通常应该避免使用提示。不过,如果其他优化技术无法解决您遇到的性能问题,查询提示可能有用。但是使用它们时应非常小心,并且应该进行全面的测试。

要使用查询提示来改善分区性能,请更改分区视图定义,使其包含该提示。

设置 @keep_partition_changes

@keep_partition_changes 是与发布关联的 TRUE/FALSE 属性;如果设置为 TRUE,它可以有效地改善分区数据集的性能。当使用分区数据集发布表时,此设置确定对一个订阅服务器的数据集进行的更改是否会为其他订阅服务器的分区带来分区维护开销。

例如,如果有两个订阅服务器,一个满足 State = 'WA',另一个满足 State = 'OR';并且某行所在的数据表的 State = 'WA' 条件被更新为 State = 'CA',那么必须从 State = 'WA' 分区中删除此行。

如果 @keep_partition_changes 为 FALSE(默认值),则合并复制进程将识别出分区数据集已更改,但无法识别出哪一个分区被更改。因此,必须为每个订阅服务器运行 sp_MSsetupbelongs 过程(用于在发布服务器填充订阅服务器的数据集),以确定订阅服务器中是否包含必须删除的行,即使该订阅服务器的分区一开始就不包含被移动的行。

如果 @keep_partition_changes 为 TRUE,则合并复制会将额外数据存储在它的系统表中,以跟踪分区更改将影响哪个订阅服务器。这意味着分区清理操作只涉及满足 State = 'WA' 的订阅服务器。

此选项的作用在于,当它为 TRUE 时,发布服务器必须存储并维护跟踪表。如果有大量订阅服务器带有离散分区和少量共享数据,则此成本可以接受。如果有少量的订阅服务器,或者这些订阅服务器共享更多的分区数据,则很可能需要根据对其他订阅服务器所做的更改更新每个订阅服务器的分区数据集。不过,可能无法接受额外的系统表数据的成本。

使用动态快照

如果发布时没有使用动态分区,则快照进程将创建一组数据文件。在第一次合并运行期间,合并进程可以将这些数据文件快速加载到每个订阅服务器中。

不过,如果发布时使用了动态分区,则快照进程不会为具有不同分区数据集的订阅服务器只创建一组适当的数据文件。在这种情况下,合并进程必须使用每个订阅服务器的动态逻辑来查询发布服务器数据库,然后将数据加载到订阅服务器中。此进程不像复制数据文件那样有效,而且,如果使用大型数据集和复杂分区表达式,它还将降低性能。

如果使用了动态快照,则可以在快照进程中使用 SQL Server 2000 的新增选项,以创建特定于订阅服务器的不同数据文件组。然后,合并进程可以将这些文件快速加载到订阅服务器中,而不必查询数据的发布服务器。有关详细信息,请参阅 SQL Server 联机丛书中的“动态快照”主题。

更改数据库及筛选设计

如果以前的优化技术未能实现所要求的性能改善目标,则可能有必要重新设计数据库,以便更好地满足复制分区需要。

重新设计数据库是改善合并复制性能的最佳解决方案,而且通常在项目早期,即与当前设计没有太多相关性时进行重新设计会更容易。如果将应用程序投入实际使用之后,才发现严重的性能问题,那么,如何从结构上更改数据库将成为极大的限制。因此,应研究那些对应用程序的其他组件影响很小,甚至完全没有影响的设计更改。两种常用的方法是使用映射表和使用非标准化数据库。

映射表

有时,基础表的结构决定了它不能用于有效的分区操作。例如,如果 CUSTOMER 表需要为每个销售代表分区,那么您很可能会向该表添加 SalesRep 列,然后将此列用作动态筛选器。但是,您可能需要使用无法有效地实施和执行索引操作的其他逻辑,例如,需要 OR 运算符逻辑、复杂表达式或复杂跨表逻辑的分区。这样,您可能将不能使用索引和基于现有表的筛选表达式。不过,您可以添加映射表,将客户与其各自的销售代表关联起来。可以使用映射表中的动态子集筛选子句表达式和联接筛选器,将 CUSTOMER 表与映射表关联起来,如下所示:

CREATE TABLE CUSTOMER
(CustID INT PRIMARY KEY.........)
CREATE TABLE CUSTOMER_REP_MAPPINGS
(MappingID PRIMARY KEY,
SalesRep VARCHAR(40),
CustID INT)

使用这些表定义,则 CUSTOMER_REP_MAPPINGS 项目的分区表达式为:

SalesRep= SUSER_SNAME()

使用该表达式,可以在两个项目之间定义联接筛选器:

CUSTOMER.CustID = CUSTOMER_REP_MAPPINGS.CustID

有关在更复杂的复制拓扑结构中使用的映射表示例,请参阅本文后面的通过实现发布服务器的层次结构,减少单个发布服务器上的合并数目一节。

非标准化

如果您使用与本文前面用作示例的 CUSTOMERORDERORDER_ITEM 表类似的层次结构分区技术,则位于关系 (CUSTOMER) 顶部的表通常具有子集筛选子句。通过约束条件与 CUSTOMER 表相关的其他表,具有标识关系的联接筛选器。此方法被称为“遵循引用”。

随着此层次结构的不断增大,子集筛选子句的逻辑与较低层表偏离得就会更远,而且分区视图会变得更加复杂,如下例所示:

CREATE TABLE CUSTOMER
(CustID INT PRIMARY KEY, 
 SalesRep VARCHAR(40)...)
CREATE TABLE ORDER
(OrderID INT PRIMARY KEY,
 CustID INT FOREIGN KEY 
      REFERENCES CUSTOMER(CustID) ...)
CREATE TABLE ORDER_ITEM
(OrderItemID INT PRIMARY KEY,
 OrderID  INT FOREIGN KEY
      REFERENCES(ORDER.OrderID)....)

CUSTOMER 项目通常包含此子集筛选子句:

CUSTOMER.SalesRep = SUSER_SNAME()

CUSTOMERORDER 表之间,ORDERORDER_ITEM 表之间也都具有联接筛选器。

通过携带在某些较低层表的子集筛选子句中使用的列,可以使表的设计非规范化,如下所示:

CREATE TABLE CUSTOMER
(CustID INT PRIMARY KEY, 
 SalesRep VARCHAR(40)...)
CREATE TABLE ORDER
(OrderID INT PRIMARY KEY,
 CustID INT,
 SalesRep VARCHAR(40),
   FOREIGN KEY (CustID, SalesRep)     
   REFERENCES   
   CUSTOMER(CustID, SalesRep)...)
CREATE TABLE ORDER_ITEM
(OrderItemID INT PRIMARY KEY,
 OrderID  INT,
 SALES_REP VARCHAR(40),
   FOREIGN KEY (OrderID, SalesRep)     
   REFERENCES   
   ORDER(OrderID, SalesRep)...)

使用此类表结构,没必要在表项目之间使用联接筛选器,每个项目均由以下子集筛选子句定义:

SalesRep = SUSER_SNAME()

根据数据库设计理论,此设计并不规范,因为 SalesRep 列是多余的,应该仅包含在顶层表中。但是,复制分区的性能好处可能超过对这些表进行非规范化操作的成本。

此技术简化了分区视图,但始终不如“遵循引用”更加有效。此技术的有效性取决于几个特定于应用程序和数据库的因素,而且,正如所有优化技术一样,应该对其进行彻底的测试。

有关优化合并复制性能的其他问题和需要考虑的因素

改善任何数据库应用程序的性能都涉及到识别进程瓶颈。本文的大部分内容都着重说明与发布服务器的分区评估关联的瓶颈。而合并复制的整体性能受到多个因素的影响,其中任何一个因素都可能是瓶颈。本节列出了许多这类因素,并提供了如何处理或深入研究这些因素的一些建议。

处理合并进程可用的资源

由于数据较多和同时使用服务器的用户数目不断增多等原因,服务器的处理需求不断增加,因此,发布服务器或订阅服务器的硬件可能无法满足作业的需求。通常,评估硬件性能包括运行 Microsoft® Windows® 系统监视器工具,此工具包含用于处理器、内存、磁盘等的性能计数器。您可以标识可升级的特定服务器硬件组件,或者可能需要将整个服务器计算机升级为型号更新或速度更快的计算机。

发布服务器/订阅服务器连接的吞吐量

根据数据量和数据库的复杂程度,合并进程可以通过网络发送大量数据。合并复制拓扑结构中的连接吞吐量可能是影响合并复制整体性能的一个因素。

发布服务器和订阅服务器应该通过可能的最高速度连接在一起:如果您正在使用低速 RAS 连接,则应考虑使用 DSL 或其他宽带技术。

缓慢连接的性能问题通常与初始化新的订阅服务器有关,特别是在将较大的数据集发送到订阅服务器时。如果您正在查看初始合并中的性能问题,您可以寻求其他方法,将初始数据集发送到新的订阅服务器中。有关详细信息,请参阅 SQL Server 联机丛书中的“应用初始快照”主题。

使用 @keep_partition_changes=TRUE 设置进行发布,可以减少不必要的分区重新评估操作,从而大大减少合并进程期间通过网络发送的数据量。将您的订阅配置为全局或匿名形式,而不是配置为局部形式也非常有用,因为这可以减少通过网络发送的复制元数据。

订阅的类型

强制订阅的合并代理程序将在发布服务器中执行,这适用于具有少量订阅服务器的发布。如果同时运行大量强制订阅服务器,则代理程序的处理资源(内存、CPU 循环)将在发布服务器的硬件上被利用,这样,SQL Server 或其他应用程序将无法使用这些资源。如果使用请求订阅或匿名订阅,则合并代理程序的处理资源将在订阅服务器计算机,而不是在发布服务器硬件上使用。

并发合并复制进程的数目

多个订阅服务器同时合并更改时,性能也会降低。更快的硬件和更有效的复制配置可以在某种程度上解决此问题。但是,即使在经过完善设计和优化的应用程序中,对于可以有效处理的并发订阅服务器合并进程的数目,每个发布服务器也都有上限值。

此问题的一种解决方案是,协调这些订阅服务器的合并进程的时间,使它们交错进行。如果目前所有订阅服务器都在上班或下班时进行合并更改,那么您可以鼓励或要求部分用户在一天的非高峰时间内执行合并进程。

如果不控制订阅服务器执行合并进程的时间,那么您可以定义允许同时进行合并的订阅服务器的数目的上限值。有关详细信息,请参阅 SQL Server 联机丛书中的 sp_addmergepublication@max_concurrent_merge 发布属性。

通过实现发布服务器的层次结构,减少单个发布服务器上的合并数目

减少发布服务器需求的另一种方法是将处理工作量分布到多个服务器上。在重新发布的层次结构中使用多个服务器是一种常见技术。例如,如果您只拥有一个服务器,用作本国家/地区内所有销售代表的发布服务器,那么还可以再添加两个发布服务器:

  • 中心发布服务器向东部和西部地区的订阅服务器发布数据。
  • 东部地区的订阅服务器将数据重新发布到东海岸地区的销售代表。
  • 西部地区的订阅服务器将数据重新发布到西海岸地区的销售代表。

可以使用前面介绍的映射表技术,定义如何为每个订阅服务器进行数据分区操作。在该例中,映射表创建了客户与销售代表之间的关联。要在层次结构中分区时使用相同技术,可以使用列(如 ReplHostName),该列识别每个服务器(在其分区中应具有客户)的 HOST_NAME() 值,如下所示:

CREATE TABLE CUSTOMER
(CustID INT PRIMARY KEY.........)

CREATE TABLE CUSTOMER_PARTITION_MAPPINGS
(MappingID PRIMARY KEY,
 ReplHostName VARCHAR(40),
 CustID int)

表 1:CUSTOMER_PARTITION_MAPPINGS 数据

MappingID ReplHostName CustID
1 East 1
2 BobJones 1
3 East 2
4 JaneGray 2
5 West 3
6 JimBrown 3

本例中,每个客户都需要两行:一行用来标识区域重发布服务器分区,另一行用来标识销售代表分区。订阅服务器 BobJones 和 JaneGray 向东部地区的重发布服务器订阅,而订阅服务器 JimBrown 向西部地区的重发布服务器订阅。每个订阅服务器的合并代理程序必须使用适合其订阅的 -Hostname 值:East、West、BobJones、JaneGray 或 JimBrown。

乍一看,有人可能会认为动态筛选表达式应该位于 CUSTOMER_PARTITION_MAPPINGS 项目的子集筛选子句中:

CUSTOMER_PARTITION_MAPPINGS.ReplHostName = HOST_NAME()

并且联接筛选器将表示这两个表之间的联接:

CUSTOMER.CustId = CUSTOMER_PARTITION_MAPPINGS.CustId

不过,如果使用此逻辑,那么东部地区的重发布服务器将只获得 CUSTOMER_PARTITION_MAPPINGS 表行(及相关客户),其中 MappingID12,也就是说,与 ReplHostName = 'East' 匹配的行。订阅服务器 BobJones 不会获得任何数据,因为在 BobJone 的东部地区重发布服务器中没有 CUSTOMER_PARTITION_MAPPINGS 行。

此发布中暗含的注意事项是,必须将整个未经筛选的 CUSTOMER_PARTITION_MAPPINGS 表复制到所有的订阅服务器上。这意味着动态筛选逻辑必须位于联接筛选表达式,而不是子集筛选子句中。CUSTOMER_PARTITION_MAPPINGS 项目不会具有子集筛选子句,而 CUSTOMER 项目的子集筛选子句将为:

CUSTOMER.CustId = CUSTOMER_PARTITION_MAPPINGS.CustId and
CUSTOMER_PARTITION_MAPPINGS.ReplHostName = HOST_NAME()

使用此筛选技术有两个好处:

  • 不需要对每种发布使用不同的筛选表达式,另外,可以对所有发布服务器使用相同的筛选表达式。
  • 分区可以通过映射表中的数据进行定义,这说明此方法非常灵活。重新发布的层次结构可以拓宽或加深,以满足今后增长的需要。

例如,如果东部地区重发布服务器中的订阅服务器的数目超过东部地区服务器的容量,则可以添加东南部地区的重发布服务器,拓宽层次结构。这需要完成以下操作:

  • 向中心发布服务器添加东南部地区订阅服务器。
  • 在东南部地区创建发布,使其成为重发布服务器。
  • CUSTOMER_PARTITION_MAPPINGS 表中,将 ReplHostName 更新为适合东南部地区客户的 SouthEast。
  • 从东部地区发布中删除东南部地区的订阅服务器,并将其添加到东南部地区的发布中。

要加深层次结构,可以添加另一个 City 层。这需要完成以下操作:

  • 向东部地区的重发布服务器中添加 NewYork 和 Boston 两个订阅服务器,并创建 New York 和 Boston 的重新发布。
  • CUSTOMER_PARTITION_MAPPINGS 表中添加 NewYork 和 Boston 客户行。
  • 从东部地区重发布服务器中删除 BobJones 和 JaneGray 订阅服务器。
  • 将 BobJones 添加到 NewYork 订阅服务器,并将 JaneGray 添加到 Boston 重发布服务器中。

在任一合并拓扑结构的重新设计过程中,每个订阅服务器的下一个合并进程都自动更新数据集。

合并进程期间的重试次数

如果合并代理程序在处理更改时遇到问题,可以在进程结束时重新尝试应用更改。处理重试行比处理普通行的效率要低得多。

如果表中存在定义的主键引用或外键引用,最容易在应用更改时导致问题。包括以下几种问题:

  • 在相关的主键行出现之前,合并进程尝试插入外键行。
  • 分区逻辑与引用完整性逻辑不同。分区逻辑可能会生成外键行,这些外键行的主键行不包含在分区数据集中;如果外键插入失败,则总是在合并进程结束时重试。

要检测合并重试,请为经过筛选、包含 sp_MSenumtriessp_MSgetonerow 过程调用的合并进程定义并运行 SQL 事件探查器跟踪。

包含文本列或图像列的表

如果已发布的表包含文本列或图像列,则这些列可以将额外处理需求强加到合并复制进程中,即使文本列或图像列并没有更改。如果定期更新表,则应考虑将文本列和图像列移到另一个表中以优化您的设计。例如,如果您有以下表:

CREATE TABLE CUSTOMER
(CustID INT PRIMARY KEY,
.
.
CustComments TEXT)

可以实现对表的超规范化操作,如下所示:

CREATE TABLE CUSTOMER
(CustID INT PRIMARY KEY,
.
.
)
CREATE TABLE CUSTCOMMENTS
(CommentID INT PRIMARY KEY,
CustID INT FOREIGN KEY
   REFERENCES CUSTOMER(CustID),
CustComment TEXT

合并复制系统表的大小

合并复制跟踪单个系统表 MSmerge_contents 中的数据更改。由于此表包含对数据库(该数据库在发布合并复制时发布)中所有表的数据更改,所以此表会变得非常庞大,对合并复制的性能造成极大的影响。

要控制系统表的大小,应定期从 MSmerge_contents 表中删除过时的或不需要的数据。如果您使用的是 SQL Server 2000 Service Pack 1 或更高版本,则基于保留的元数据清理进程将自动删除那些过于陈旧、已超出了发布的 @retention 属性的更改元数据。在存在许多用户或大量更改的情况下,将 @retention 属性减少到可以满足应用程序需要的最低值,可以大大改善合并应用程序的性能。

除控制系统表的大小外,还应考虑是否有必要通过合并复制来复制所有表。例如,引用数据或搜索数据通常是相对静止的,更适合不涉及合并复制系统表的快照复制。还可以使用自定义技术更新这些表,而不涉及复制。

如果前面介绍的技术不能有效控制系统表的大小,您可以分离发布的数据,将其放入不同数据库的发布中。将数据放入同一数据库的不同发布中不会改善性能,因为数据库内的所有发布都使用一个 MSmerge_contents 表。此项技术假设可以在逻辑上分离数据。(请记住,所有与联接筛选器相关的数据必须在同一发布中。)

已发布的表中的数据总量

通常情况下,与较小的表相比,包含大量行的表需要更多的处理资源。本文前面讨论的分区操作的目的之一,就是要减小订阅服务器数据库中的表的大小。但也可以将过时数据移到存档表或数据库中,以减小已发布的表的大小。

在每个合并进程中复制的更改量

必须根据合并进程的频率衡量每个合并进程中的更改量。与经常进行合并的订阅服务器相比,较少进行合并的订阅服务器通常要处理更多的更改。通过使订阅服务器更频繁地进行合并,可以在某种程度上控制每个合并中复制的更改量。正如上文所述,还可以减少发布的 @retention 属性值,以避免订阅服务器过时。

合并代理程序属性

这些属性都可能影响合并进程的性能,可以通过合并代理程序的配置文件,或者通过手动更改 SQL Server 企业管理器中的合并代理程序作业步骤的属性来调整它们。

-Continuous、-PollingInterval

-Continuous 属性强制合并进程在处理的上载和下载阶段无限循环,每个循环都不会断开连接或重新连接。尽管这并不影响分区处理本身,但在存在要复制的当前更改时,它的确使合并进程保持为最新。保持最新有助于避免复制不经常进行的合并中的大量更改。

还可以通过调整合并代理程序作业安排的 -PollingInterval 设置,来调整合并进程的频率。但是,当应用程序需要几乎实时运行,并且发布服务器定期从订阅服务器接收最新更改时,使用 -Continuous 通常更合适。因为它避免了每隔几分钟运行已安排的代理程序作业时,所产生的连接/初始化/断开连接等系统开销。

-Validate、-ValidateInterval

-Validate 属性指定合并所有更改后是否比较发布服务器和订阅服务器中的数据,以确保它们是同步的。完成此额外进程需要花费很长时间,并且可能需要大量的查询处理资源。使用 -ValidateInterval 属性可以控制合并进程期间执行验证的频率。

-HistoryVerboseLevel、-OutputVerboseLevel

这两个属性控制由合并进程记录的信息量。当大量的订阅服务器交换相对较小的一组数据更改时,减小这些值可以改善性能。

-MaxBcpThreads

此属性控制大容量复制程序操作期间使用的线程数。此设置只影响新的订阅服务器的初始化。

-SrcThreads、-DestThreads

这两个属性控制发布中可以用于处理项目的线程数。在具有大量内存和处理资源的服务器上,增大这些值可以使合并进程以并行方式执行更多的操作。

-MaxDownloadChanges、-MaxUploadChanges 与批处理大小参数

这些设置都可以控制合并进程一次处理的更改数:

  • -MaxDownloadChanges 和 -MaxUploadChanges
  • -UploadGenerationsPerBatch 和 -DownloadGenerationsPerBatch
  • -UploadReadChangesPerBatch 和 -DownloadReadChangesPerBatch
  • -UploadWriteChangesPerBatch 和 -DownloadWriteChangesPerBatch

如果有大量更改需要处理,并且发布服务器和订阅服务器之间存在高速连接,增大这些值可能会导致吞吐量提高,因为合并进程每次进行批处理的开销减少了。

而如果发布服务器和订阅服务器之间是低速连接或不可靠连接,减小这些值则有助于确保全部完成较小的批处理,从而避免出现不完整批处理所造成的高成本的重试操作。

-ExchangeType

此属性的默认值 (-ExchangeType 3) 强制合并进程上载订阅服务器的更改,然后下载发布服务器的更改。可以使用此属性指定仅上载 (-ExchangeType 1) 或仅下载 (-ExchangeType 2)。

当多个订阅服务器中存在大量更改,并且每个订阅服务器中的更改都影响其他订阅服务器的数据集时,指定 12 可能有用。在这种情况下,每个订阅服务器都上载其更改(使用 -ExchangeType 1),而不会下载任何更改。当所有订阅服务器都上载了各自的更改后,就可以使用 -ExchangeType 2 来下载组合的更改集。此方法可以减少同时执行几个双向合并,以添加所有订阅服务器的更改时产生的系统开销。

总结

对于很多分布式应用程序,合并复制都是一种理想的解决方案,特别适合在多个站点更新数据和使用断开连接的订阅服务器。不过,要使合并复制有效,需注意以下几点:

  • 执行合并复制的方式必须与有效分区查询处理一致。
  • 必须认真设计建立应用程序所需的数据库,并通过复制进行优化。

由于合并复制使用标准 SQL Server 查询、过程、表和视图,因此您可以观察合并复制进程,特别是分区进程,还可以使用这些对象,相应地调整和优化应用程序。

对于很多合并复制应用程序,对数据进行分区是最基本的操作,用于强制实施业务规则和改善性能。不过,如果没有正确实现,则可能会成为导致性能问题的主要原因。遵循本文中介绍的这些进程和建议,可以帮助您实现可扩展的、高性能的合并复制应用程序。