如何配置和排查 SQL Server 中分发代理的 SubscriptionStreams 参数的问题

原始产品版本: SQL Server(所有支持的版本)
原始 KB 数: 953199

本文提供有关参数 SubscriptionStreams的详细信息、使用此参数时的最佳做法以及关联的故障排除。

简介

该参数 SubscriptionStreams 可用于控制连接数。 在 Microsoft SQL Server 中的事务复制中,可以使用参数启用多个连接,分发代理使用该连接并行向订阅服务器应用批更改。 此操作极大地提高了复制吞吐量。 同时,分发代理仍可以保留许多与分发代理使用单个连接应用更改时相同的事务特征。 如果有一个连接无法执行或提交,则所有连接将中止当前批处理,而且代理将用单独的流重试失败的批处理。 在重试阶段完成之前,订阅服务器上会存在临时事务不一致。 失败的批处理成功提交后,订阅服务器将恢复到事务一致状态。

为参数 SubscriptionStreams指定值 2 或更大时,在订阅服务器上接收事务的顺序可能与在发布服务器上执行事务的顺序不同。 如果此行为在同步期间导致约束冲突,则应使用 NOT FOR REPLICATION 此选项在同步期间禁用约束强制实施。 有关详细信息,请参阅 同步中的触发器和约束的控制行为。

启用 SubscriptionStreams 之前要考虑的因素

SubscriptionStreams 主要负责从分发服务器到订阅服务器的延迟,因此在决定前往 SubscriptionStreams之前,请确保确实遇到了从分发服务器到订阅服务器的延迟。 可以在复制监视器中使用跟踪令牌,也可以在 SQLServer:Replication Dist 等性能监视器计数器。>Dist:Delivery Latency to have a idea of the level of latency.

从分发服务器到订阅服务器的延迟可能是由多种原因引起的,包括但不限于以下原因:

  • 在分发服务器或订阅服务器上阻止
  • 分发服务器或订阅服务器中的任何瓶颈,如磁盘驱动器缓慢、网络带宽缓慢和过时统计信息
  • 来自发布服务器的批量事务
  • 来自发布服务器的传入事务速率过高
  • 订阅数据库中的触发器或不必要的索引

数据库管理员(DBA)需要拨打电话并测试是否 SubscriptionStreams 要帮助他们。 例如,在订阅服务器上阻止时,增加并发连接数不会有所帮助,但可能会使情况变得更糟。 而在发布服务器传入事务速率过高的情况下,你认为分发代理的单个线程无法应付传入的负载,则可以考虑将参数SubscriptionStreams的值增加到 >=2。 它还可能有助于网络缓慢和磁盘速度缓慢的情况。 理想情况下,此参数的最大值为 64,但建议的值(或起始值)等于目标(订阅服务器)的物理处理器数。

如何配置参数 SubscriptionStreams

SubscriptionStreams是复制监视器中分发代理配置文件中不可见的参数之一。 可以使用 sp_addsubscription (Transact-SQL) 为此代理参数@subscriptionstreams指定值,或者使用以下过程将此参数添加到分发代理作业命令部分:

  1. 打开复制监视器,展开 “我的发布服务器”,然后在左窗格窗口中选择发布。 在右窗格窗口中的“所有订阅”部分下,你将看到此发布的所有订阅者的列表。

  2. 右键单击要启用参数 SubscriptionStreams 的订阅服务器,然后选择“ 查看详细信息”。 此时会弹出一个新窗口,其中包含分发代理会话详细信息。

  3. 在此新窗口中,选择顶部菜单栏中的操作,然后选择分发代理作业属性。 这将打开分发代理的“作业属性”窗口。

  4. 在左窗格窗口中选择 “步骤 ”,然后在右窗格窗口中选择“ 运行代理 ”,然后选择“ 编辑”。 此时会弹出一个新窗口。

  5. 滚动到命令部分(最右侧)的末尾,并追加此参数 -SubscriptionStreams 6

  6. 保存设置并重启分发代理作业。 实现更改需要重启分发代理。

注意

在上面的示例中,SubscriptionStreams设置为 6,这意味着我们要在订阅服务器上查找六个并行连接,以便分发代理。 可以根据环境和测试设置此数字。

确定流数

可以使用参数 SubscriptionStreams来注意到性能改进。 如果有改进,则改进可能是名义上的。 很难确定市场上每个磁盘子系统将通过使用 SubscriptionStreams哪种类型的性能改进。 因此,建议准备模拟生产环境的测试环境。 可以使用各种配置值和不使用的方案来测试使用SubscriptionStreams的方案SubscriptionStreams

建议对发布和订阅执行负载测试,以确定可以使用它获取 SubscriptionStreams的性能改进。 应执行性能基线测试,以了解磁盘子系统的预期吞吐量。 在执行每个测试之前,请应用许多更改,在发布服务器上创建负载。 创建负载时,请确保分发代理未运行。 当复制具有足够的延迟时,请运行分发代理以测试以下配置的性能:

  • 请勿使用参数 SubscriptionStreams
  • 将值 SubscriptionStreams 设置为等于服务器上的处理器数。 例如,如果服务器具有 8 个处理器,请将值 SubscriptionStreams 设置为 8。
  • 指定不同的值以获取 SubscriptionStreams 最佳配置。

执行测试时,可以监视分发代理的以下性能计数器:

  • Dist: Delivered Cmds/sec
  • Dist:传递延迟

指定参数 SubscriptionStreams 后分发代理的行为

分发代理维护在SubscriptionStreams指定的会话/连接数。 分发代理使用这些会话在订阅服务器上应用更改。

但是,在指定SubscriptionStreams并运行分发代理一段时间后,分发代理可能会切换到仅使用一个会话将更改应用到订阅服务器。

分发代理仅使用一个会话的原因

由于多种原因,分发代理可能只切换到使用一个会话。 以下是最常见的原因:

  • 当分发代理应用更改时,其中一个会话将引发错误。

    例如,分发代理使用一个会话将行插入子表中。 如果在分发代理使用另一个会话将相应行插入父表中之前发生此情况,则外键约束冲突将引发错误消息。

  • 阻塞监视器线程检测到阻止。 由于以下原因之一,可能会发生阻止:

    • 分发代理使用不同的会话对订阅服务器上的表执行INSERT操作UPDATE和操作。 如果表包含唯一的非聚集索引,则当分发代理更新表的索引键时,可能会发生两个会话之间的阻塞。

    • 在订阅服务器上,分发代理对多个表运行数据操作语言(DML)语句。 如果对这些表定义了索引视图,则当索引视图更新共享索引键时,可能会发生两个会话之间的阻塞。

    • 分发代理使用一个会话针对订阅服务器上的表运行 DML 语句。 此表定义了 DML 触发器。 DML 触发器对使用另一个会话更新的另一个表运行 DML 语句。 在这种情况下,可能会发生两个会话之间的阻塞。

强烈建议不要在订阅服务器数据库中使用以下数据库对象:

  • 外键约束
  • 唯一的非聚集索引
  • 索引视图
  • 可能导致会话之间阻塞的 DML 触发器

如何确定分发代理是否仅使用一个会话

为此,请使用下列方法之一:

注意

尽管可以使用方法 1 确认分发代理尚未切换到使用一个会话,但必须使用方法 2 或方法 3 来确认分发代理已切换到使用一个会话。

  • 方法 1

    查询动态管理视图(DMV) sys.dm_exec_sessions 以获取与订阅数据库的连接会话。 如果只看到一个连接会话,则分发代理可能已切换到使用一个会话。 如果看到多个连接会话,分发代理仍在使用指定的会话数。

    若要确认分发代理已切换到使用一个会话,请使用方法 2 或方法 3。

  • 方法 2

    查询分布数据库中表msdistribution_historycomments。 如果查询的结果包含以下条目,则分发代理已切换到使用一个会话:

    进程未能在多流式处理模式下完成最后一批,该进程已重置为单个连接模式,并且正在重试该操作。

  • 方法 3

    检查分发代理的输出文件。 如果输出文件包含与方法 2 相同的错误消息,则分发代理已切换到仅使用一个会话。

    以下输出文件是一个示例:

    Date/Time 100 transaction(s) with 1181 command(s) were delivered. 
    Date/Time 100 transaction(s) with 2672 command(s) were delivered. 
    Date/Time Bucket 6 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 1 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 3 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 0 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 5 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 2 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 7 aborted the wait for Ready To Commit event, deadlock found between spid 117 and 114 
    Date/Time Bucket 4 aborted the wait for Ready To Commit event, due to thread shutdown event 
    ... 
    Date/Time Number of subscription streams has been reset from 8 to 1, state 4. 
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    Date/Time Disconnecting from Subscriber 
    SQLInstance 
    
    
    Date/Time Connecting to Subscriber 
    SQLInstance 
    
    Date/Time The process failed to complete last batch in multi-streaming mode, it has been reset to single connection mode and is retrying the operation. 
    Date/Time 21 transaction(s) with 390 command(s) were delivered.
    

如何排查切换为仅使用一个会话的分发代理问题

  1. 在订阅服务器上运行 SQL Server Profiler 以捕获阻止的进程报告事件和异常事件。 这些事件记录了分发代理应用更改时发生的阻塞和错误。

    注意

    异常事件可能是由与问题关联的任何类型的错误引起的。 例如,此错误可能是由外键约束冲突引起的。

  2. 使用“如何”中的其中一种方法来确定分发代理是否只使用一个会话部分来监视分发代理。

  3. 如果分发代理已切换到使用一个会话,请停止跟踪。

  4. 从分发代理的输出文件或表msdistribution_historystart_time获取以下条目的时间戳:

    进程未能在多流式处理模式下完成最后一批,该进程已重置为单个连接模式,并且正在重试该操作。

  5. 从订阅服务器打开跟踪 (.trc) 文件。 找到阻塞脚本或异常事件,其时间戳与在步骤 4 中获取的时间戳相同或非常接近。

  6. 如果注意到异常,请检查异常的详细信息以确定原因。 例如,异常可能是由外键约束冲突引起的。 如果是这样,我们建议删除订阅服务器数据库中的外键约束。

    如果注意到阻止脚本,问题是由阻止引起的。 下面是一个阻止脚本示例:

    <blocked-process-report monitorLoop="41589"> 
        <blocked-process> 
            <process id="process3a6d438" taskpriority="0" logused="24592" waitresource="KEY: 6:72057594375700480 (0100e420fa5a)" waittime="9937" ownerId="568644832" transactionname="user_transaction" lasttranstarted="2008-05-05T04:55:04.430" XDES="0xa5619e370" lockMode="X" schedulerid="11" kpid="6104" status="suspended" spid="58" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2008-05-05T04:55:04.553" lastbatchcompleted="2008-05-05T04:55:04.430" clientapp=<DistributionAgentProgram> hostname=<servername> hostpid="3980" loginname=<SQLAgentAcct>  isolationlevel="read committed (2)" xactid="568644832" currentdb="6" lockTimeout="4294967295" clientoption1="671090784" clientoption2="128056"> 
                <executionStack> 
                <frame line="5" stmtstart="642" stmtend="1600" sqlhandle="0x0300060057a14477a8c6dd00609a00000100000000000000"/> 
                </executionStack> 
                <inputbuf> 
                Proc [Database Id = 6 Object Id = 2000986455]
                </inputbuf> 
            </process> 
        </blocked-process> 
        <blocking-process> 
            <process status="sleeping" spid="68" sbid="0" ecid="0" priority="0" transcount="1" lastbatchstarted="2008-05-05T04:55:04.570" lastbatchcompleted="2008-05-05T04:55:05.103" clientapp=<DistributionAgentProgram> hostname=<servername> hostpid="3980" loginname=<SQLAgentAcct> isolationlevel="read committed (2)" xactid="568644998" currentdb="6" lockTimeout="4294967295" clientoption1="671090784" clientoption2="128056"> 
            <executionStack/> 
            <inputbuf> 
            Proc [Database Id = 6 Object Id = 1172459501]
            </inputbuf> 
            </process> 
        </blocking-process> 
    </blocked-process-report> 
    

    阻止脚本记录阻止的会话和阻止会话。 阻止的会话从标记 <blocked-process>开始。 阻止会话从标记 <blocking-process>开始。

  7. Object Id 阻止的会话和阻止会话中找到对象 Proc

    在示例阻止脚本中,Object IdProc阻止的会话是 2000986455Object Id Proc阻塞会话中为 1172459501.

  8. 在订阅数据库中,通过指定列object_id等于在步骤 7 中获取的对象 ID 来查询视图 sys.objects。 执行此操作时,可以确定对象名称。

    例如,在订阅数据库的上下文中运行以下查询:

    USE <SubDBName> 
    GO 
    SELECT name FROM sys.objects 
    WHERE object_id = 1172459501 OR object_id = 2000986455 
    

    注意

    • 占位符 <SubDBName> 表示订阅数据库的名称。
    • 通常,这些对象是复制中使用的存储过程。
  9. 确定导致阻塞的索引或索引视图。 为此,请按照下列步骤进行操作:

    1. 在阻止脚本中,找到属性 waitresource的值。

      在示例阻塞脚本中,值为 waitresource 72057594375700480.

    2. 通过指定列PARTITION_ID等于waitresource在步骤 9a 中获取的值,查询视图 sys.partitions 以获取对象 ID 和索引 ID。

      例如,运行下列查询:

      SELECT object_id, index_id FROM SYS.PARTITIONS WHERE PARTITION_ID=72057594375700480
      
    3. 在订阅数据库中,查询视图 sys.indexes ,以使用在步骤 9b 中获取的对象 ID 和索引 ID 来确定索引。

      例如,运行下列查询:

      USE <SubDBName> 
      GO 
      SELECT name, type_desc, is_unique FROM sys.indexes 
      WHERE object_id = <objID> and index_id = <idxID>
      

      注意

      • 占位符 <objID> 表示在步骤 9b 中获取的对象 ID。
      • 占位符 <idxID> 表示在步骤 9b 中获取的索引 ID。
  10. 如果阻止是由索引视图引起的,建议删除索引视图。 如果阻止是由唯一的非聚集索引引起的,建议删除索引,然后重新创建非唯一索引。

阻止监视器线程的说明

分发代理维护阻止监视器线程,该线程可检测会话之间的阻塞。 如果阻止监视器线程检测到会话之间的阻塞,则分发代理切换到使用一个会话重新应用 分发代理以前无法应用的当前一批命令。

有关阻止监视器线程的详细信息,请查看 阻止监视器线程

分发代理如何恢复多个会话

在分发代理可以恢复多个会话之前,分发代理必须执行存储过程sp_MSget_repl_commands来重新查询尚未在订阅服务器上应用的命令的分发数据库。 然后,分发代理必须在订阅服务器上应用所有这些命令,分发代理才能恢复多个会话。 在潜在的复制环境中,分发代理无法恢复多个会话,因为分发代理必须在订阅服务器上应用多个命令,分发代理才能恢复多个会话。

若要跟踪整个过程,请检查分发代理的输出文件。