对在两台服务器上具有显著性能差异的查询进行故障排除

适用范围:SQL Server

本文提供了性能问题的故障排除步骤,其中查询在一台服务器上运行速度比另一台服务器上慢。

现象

假设有两台安装了 SQL Server 的服务器。 其中一个 SQL Server 实例包含另一个 SQL Server 实例中的数据库副本。 针对两台服务器上的数据库运行查询时,查询在一台服务器上运行的速度比另一个服务器慢。

以下步骤可帮助排查此问题。

步骤 1:确定这是多个查询的常见问题

使用以下两种方法之一比较两台服务器上两个或多个查询的性能:

  • 在两台服务器上手动测试查询:

    1. 选择多个查询,以便对以下查询进行优先级测试:
      • 一台服务器上的速度比另一台服务器上要快得多。
      • 对用户/应用程序很重要。
      • 经常执行或设计为按需重现问题。
      • 足够长的时间捕获其上的数据(例如,而不是 5 毫秒的查询,请选择 10 秒的查询)。
    2. 在两台服务器上运行查询。
    3. 比较每个查询的两个服务器上的已用时间(持续时间)。
  • 使用 SQL Nexus 分析性能数据。

    1. 为两台服务器上的查询收集 PSSDiag/SQLdiagSQL LogScout 数据。
    2. 使用 SQL Nexus 导入收集的数据文件,并比较两个服务器的查询。 有关详细信息,请参阅两个日志集合之间的性能比较(例如慢速和快速)。

方案 1:在两个服务器上只执行一个查询不同

如果只有一个查询以不同的方式执行,则问题更有可能特定于单个查询,而不是特定于环境。 在这种情况下,请转到 步骤 2:收集数据并确定性能问题的类型。

方案 2:在两个服务器上执行多个查询的方式不同

如果多个查询在一台服务器上运行速度比另一个服务器慢,则最可能的原因是服务器或数据环境的差异。 转到 “诊断环境差异 ”,查看两个服务器之间的比较是否有效。

步骤 2:收集数据并确定性能问题的类型

收集已用时间、CPU 时间和逻辑读取

若要在两台服务器上收集查询的运行时间和 CPU 时间,请使用最适合你的情况的以下方法之一:

  • 对于当前正在执行的语句,请检查sys.dm_exec_requests中的total_elapsed_timecpu_time列。 运行以下查询以获取数据:

    SELECT 
        req.session_id
        , req.total_elapsed_time AS duration_ms
        , req.cpu_time AS cpu_time_ms
        , req.total_elapsed_time - req.cpu_time AS wait_time
        , req.logical_reads
        , SUBSTRING (REPLACE (REPLACE (SUBSTRING (ST.text, (req.statement_start_offset/2) + 1, 
           ((CASE statement_end_offset
               WHEN -1
               THEN DATALENGTH(ST.text)  
               ELSE req.statement_end_offset
             END - req.statement_start_offset)/2) + 1) , CHAR(10), ' '), CHAR(13), ' '), 
          1, 512)  AS statement_text  
    FROM sys.dm_exec_requests AS req
        CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) AS ST
    ORDER BY total_elapsed_time DESC;
    
  • 对于查询的过去执行,请检查sys.dm_exec_query_stats中的last_elapsed_timelast_worker_time列。 运行以下查询以获取数据:

    SELECT t.text,
         (qs.total_elapsed_time/1000) / qs.execution_count AS avg_elapsed_time,
         (qs.total_worker_time/1000) / qs.execution_count AS avg_cpu_time,
         ((qs.total_elapsed_time/1000) / qs.execution_count ) - ((qs.total_worker_time/1000) / qs.execution_count) AS avg_wait_time,
         qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
         qs.total_logical_writes / qs.execution_count AS avg_writes,
         (qs.total_elapsed_time/1000) AS cumulative_elapsed_time_all_executions
    FROM sys.dm_exec_query_stats qs
         CROSS apply sys.Dm_exec_sql_text (sql_handle) t
    WHERE t.text like '<Your Query>%'
    -- Replace <Your Query> with your query or the beginning part of your query. The special chars like '[','_','%','^' in the query should be escaped.
    ORDER BY (qs.total_elapsed_time / qs.execution_count) DESC
    

    注意

    如果 avg_wait_time 显示负值,则它是并行 查询

  • 如果可以在 SQL Server Management Studio(SSMS)或 Azure Data Studio 中按需执行查询,请使用 SET STATISTICS TIME 和 SET STATISTICS IO ON 运行它ON

    SET STATISTICS TIME ON
    SET STATISTICS IO ON
    <YourQuery>
    SET STATISTICS IO OFF
    SET STATISTICS TIME OFF
    

    然后,从 消息中,你将看到 CPU 时间、已用时间和逻辑读取,如下所示:

      Table 'tblTest'. Scan count 1, logical reads 3, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0.
    
      SQL Server Execution Times:
        CPU time = 460 ms,  elapsed time = 470 ms.
    
  • 如果可以收集查询计划,请检查执行计划属性中的数据

    1. 运行包含 实际执行计划的 查询。

    2. 执行计划中选择最左侧的运算符。

    3. “属性”展开 QueryTimeStats 属性。

    4. 检查 运行时间CpuTime

      SQL Server 执行计划属性窗口的屏幕截图,其中展开了 QueryTimeStats 属性。

比较查询的运行时间和 CPU 时间,以确定这两个服务器的问题类型。

类型 1:CPU 绑定(运行程序)

如果 CPU 时间接近、等于或高于已用时间,则可以将其视为 CPU 绑定查询。 例如,如果已用时间为 3000 毫秒(ms),并且 CPU 时间为 2900 毫秒,则表示大部分已用时间都花在 CPU 上。 然后,我们可以说这是一个 CPU 绑定的查询。

运行(CPU 绑定)查询的示例:

已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
3200 3000 300000
1080 1000 20

逻辑读取 - 读取缓存中的数据/索引页 - 通常是 SQL Server 中 CPU 使用率的驱动因素。 在某些情况下,CPU 使用来自其他源:一个 while 循环(在 T-SQL 或其他代码(如 XProcs 或 SQL CRL 对象)中)。 表中的第二个示例演示了这种情况,其中大多数 CPU 不是来自读取。

注意

如果 CPU 时间大于持续时间,则表示执行并行查询;多个线程同时使用 CPU。 有关详细信息,请参阅 并行查询 - 运行程序或服务员

类型 2:等待瓶颈(服务员)

如果已用时间明显大于 CPU 时间,则查询正在等待瓶颈。 已用时间包括对 CPU(CPU 时间)执行查询的时间,以及等待释放资源的时间(等待时间)。 例如,如果经过的时间为 2000 毫秒,并且 CPU 时间为 300 毫秒,则等待时间为 1700 毫秒(2000 - 300 = 1700)。 有关详细信息,请参阅 “等待类型”。

等待查询的示例:

已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
2000 300 28000
10080 700 80000

并行查询 - 运行程序或服务员

并行查询可能会使用比总持续时间更多的 CPU 时间。 并行度的目标是允许多个线程同时运行查询的各个部分。 在时钟时间的 1 秒内,查询可以通过执行 8 个并行线程来使用 8 秒的 CPU 时间。 因此,根据已用时间和 CPU 时间差确定 CPU 绑定或等待查询变得困难。 但是,作为一般规则,请遵循上述两节中列出的原则。 摘要为:

  • 如果已用时间远远大于 CPU 时间,请考虑它为服务员。
  • 如果 CPU 时间大于已用时间,请考虑它为运行程序。

并行查询的示例:

已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
1200 8100 850000
3080 12300 1500000

步骤 3:比较两台服务器中的数据、找出方案并排查问题

假设有两台名为 Server1 和 Server2 的计算机。 查询在 Server1 上运行的速度比 Server2 慢。 比较两个服务器中的时间,然后按照最符合以下部分中你情况的操作进行操作。

方案 1:Server1 上的查询使用更多的 CPU 时间,而 Server1 上的逻辑读取数比 Server2 上的逻辑读取更高

如果 Server1 上的 CPU 时间远大于 Server2,并且运行时间与两台服务器上的 CPU 时间匹配,则不会发生重大等待或瓶颈。 Server1 上的 CPU 时间增加很可能是由逻辑读取增加引起的。 逻辑读取的重大更改通常表示查询计划的差异。 例如:

Server 已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
Server1 3100 3000 300000
服务器 2 1100 1000 90200

操作:检查执行计划和环境

  1. 比较两台服务器上的查询的执行计划。 为此,请使用以下两种方法之一:
  2. 比较环境。 不同的环境可能会导致查询计划差异或 CPU 使用率的直接差异。 环境包括服务器版本、数据库或服务器配置设置、跟踪标志、CPU 计数或时钟速度以及虚拟机与物理机。 有关详细信息,请参阅诊断查询计划差异。

方案 2:查询是 Server1 而不是 Server2 上的服务员

如果两台服务器上的查询的 CPU 时间相似,但 Server1 上的已用时间远大于 Server2,则 Server1 上的查询会花费更长的时间 等待瓶颈。 例如:

Server 已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
Server1 4500 1000 90200
服务器 2 1100 1000 90200
  • Server1 上的等待时间:4500 - 1000 = 3500 毫秒
  • Server2 上的等待时间:1100 - 1000 = 100 毫秒

操作:检查 Server1 上的等待类型

识别并消除 Server1 上的瓶颈。 等待示例包括阻塞(锁定等待)、闩锁等待、磁盘 I/O 等待、网络等待和内存等待。 若要排查常见瓶颈问题,请继续 诊断等待或瓶颈

方案 3:两台服务器上的查询都是服务员,但等待类型或时间不同

例如:

Server 已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
Server1 8000 1000 90200
服务器 2 3000 1000 90200
  • Server1 上的等待时间:8000 - 1000 = 7000 毫秒
  • Server2 上的等待时间:3000 - 1000 = 2000 毫秒

在这种情况下,两台服务器上的 CPU 时间相似,这表示查询计划可能相同。 如果两个服务器没有等待瓶颈,查询将同样执行。 因此,持续时间差异来自不同的等待时间量。 例如,查询在 Server1 上的锁上等待 7000 毫秒,而它在 Server2 上的 I/O 上等待 2000 毫秒。

操作:检查两台服务器上的等待类型

解决每个瓶颈在每个服务器上单独等待,并加快这两个服务器上的执行速度。 此问题的故障排除很费力,因为你需要消除两个服务器上的瓶颈,并使性能可比。 若要排查常见瓶颈问题,请继续 诊断等待或瓶颈

方案 4:Server1 上的查询使用的 CPU 时间多于 Server2,但逻辑读取已关闭

例如:

Server 已用时间 (ms) CPU 时间(毫秒) 读取数(逻辑)
Server1 3000 3000 90200
服务器 2 1000 1000 90200

如果数据符合以下条件:

  • Server1 上的 CPU 时间比 Server2 上的 CPU 时间要大得多。
  • 经过的时间与每个服务器上的 CPU 时间紧密匹配,这表示没有等待。
  • 逻辑读取(通常是 CPU 时间的最高驱动程序)在两台服务器上相似。

然后,额外的 CPU 时间来自其他一些 CPU 绑定的活动。 此方案是所有方案中最罕见的情况。

原因:跟踪、UDF 和 CLR 集成

此问题可能是由以下原因引起的:

  • XEvents/SQL Server 跟踪,尤其是对文本列进行筛选(数据库名称、登录名、查询文本等)。 如果在一台服务器上启用了跟踪,但另一台服务器上未启用跟踪,这可能是差异的原因。
  • 用户定义函数(UDF)或其他执行 CPU 绑定操作的 T-SQL 代码。 这通常是 Server1 和 Server2 上其他条件不同的原因,例如数据大小、CPU 时钟速度或电源计划。
  • SQL Server CLR 集成扩展存储过程(XP) 可能会驱动 CPU,但不执行逻辑读取。 DLL 的差异可能会导致不同的 CPU 时间。
  • CPU 绑定的 SQL Server 功能的差异(例如字符串操作代码)。

操作:检查跟踪和查询

  1. 检查两台服务器上的跟踪是否存在以下内容:

    1. 如果在 Server1 上启用了任何跟踪,但未在 Server2 上启用跟踪。
    2. 如果启用了任何跟踪,请禁用跟踪并在 Server1 上再次运行查询。
    3. 如果这次查询运行速度更快,请启用跟踪回溯,但从中删除文本筛选器(如果有)。
  2. 检查查询是否使用执行字符串操作的 UDF,或者对列表中的数据列 SELECT 执行大量处理。

  3. 检查查询是否包含循环、函数递归或嵌套。

诊断环境差异

检查以下问题,并确定两个服务器之间的比较是否有效。

  • 两个 SQL Server 实例是同一版本还是内部版本?

    如果没有,则可能存在导致差异的一些修复。 运行以下查询以获取两个服务器上的版本信息:

    SELECT @@VERSION
    
  • 这两台服务器上的物理内存量是否相似?

    如果一台服务器具有 64 GB 内存,而另一台服务器的内存为 256 GB,那将是一个重大差异。 由于有更多的内存可用于缓存数据/索引页和查询计划,可以根据硬件资源可用性以不同的方式优化查询。

  • 这两台服务器上的 CPU 相关硬件配置是否相似? 例如:

    • 计算机(一台计算机上的 24 个 CPU 与另一台计算机上的 96 个 CPU)之间存在差异。

    • 电源计划 - 均衡与高性能。

    • 虚拟机(VM)与物理(裸机)计算机。

    • Hyper-V 与 VMware - 配置的差异。

    • 时钟速度差异(时钟速度较低,时钟速度高于更高的时钟速度)。 例如,2 GHz 与 3.5 GHz 可能会有所作为。 若要在服务器上获取时钟速度,请运行以下 PowerShell 命令:

      Get-CimInstance Win32_Processor | Select-Object -Expand MaxClockSpeed
      

    使用以下两种方法之一测试服务器的 CPU 速度。 如果它们不产生可比的结果,则问题不在 SQL Server 之外。 这可能是电源计划差异、CPU 减少、VM 软件问题或时钟速度差异。

    • 在两台服务器上运行以下 PowerShell 脚本,并比较输出。

      $bf = [System.DateTime]::Now
      for ($i = 0; $i -le 20000000; $i++) {}
      $af = [System.DateTime]::Now
      Write-Host ($af - $bf).Milliseconds " milliseconds"
      Write-Host ($af - $bf).Seconds " Seconds"
      
    • 在两个服务器上运行以下 Transact-SQL 代码,并比较输出。

      SET NOCOUNT ON 
      DECLARE @spins INT = 0
      DECLARE @start_time DATETIME = GETDATE(), @time_millisecond INT
      
      WHILE (@spins < 20000000)
      BEGIN
         SET @spins = @spins +1
      END
      
      SELECT @time_millisecond = DATEDIFF(millisecond, @start_time, getdate())
      
      SELECT @spins Spins, @time_millisecond Time_ms,  @spins / @time_millisecond Spins_Per_ms
      

诊断等待或瓶颈

若要优化正在等待瓶颈的查询,请确定等待的时间以及瓶颈的位置(等待类型)。 确认等待类型后,请减少等待时间或完全消除等待时间。

若要计算近似等待时间,请从查询运行时间中减去 CPU 时间(工作时间)。 通常,CPU 时间是实际执行时间,查询生存期的剩余部分正在等待。

如何计算近似等待持续时间的示例:

已用时间 (ms) CPU 时间(毫秒) 等待时间(ms)
3200 3000 200
7080 1000 6080

确定瓶颈或等待

  • 若要标识历史长时间等待查询(例如, >20% 的总运行时间是等待时间),请运行以下查询。 自 SQL Server 启动以来,此查询使用缓存查询计划的性能统计信息。

    SELECT t.text,
             qs.total_elapsed_time / qs.execution_count
             AS avg_elapsed_time,
             qs.total_worker_time / qs.execution_count
             AS avg_cpu_time,
             (qs.total_elapsed_time - qs.total_worker_time) / qs.execution_count
             AS avg_wait_time,
             qs.total_logical_reads / qs.execution_count
             AS avg_logical_reads,
             qs.total_logical_writes / qs.execution_count
             AS avg_writes,
             qs.total_elapsed_time
             AS cumulative_elapsed_time
    FROM sys.dm_exec_query_stats qs
             CROSS apply sys.Dm_exec_sql_text (sql_handle) t
    WHERE (qs.total_elapsed_time - qs.total_worker_time) / qs.total_elapsed_time
             > 0.2
    ORDER BY qs.total_elapsed_time / qs.execution_count DESC
    
  • 若要识别当前执行时间超过 500 毫秒的查询,请运行以下查询:

    SELECT r.session_id, r.wait_type, r.wait_time AS wait_time_ms
    FROM sys.dm_exec_requests r 
       JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id 
    WHERE wait_time > 500
    AND is_user_process = 1
    
  • 如果可以收集查询计划,请从 SSMS 中的执行计划属性检查 WaitStats

    1. 运行包含 实际执行 计划的查询。
    2. 在“执行计划”选项卡中右键单击最左侧的运算符
    3. 选择“属性,然后选择 WaitStats 属性。
    4. 检查 WaitTimeMsWaitType
  • 如果熟悉 PSSDiag/SQLdiagSQL LogScout LightPerf/GeneralPerf 方案,请考虑使用其中任一方案收集性能统计信息并识别 SQL Server 实例上的等待查询。 可以使用 SQL Nexus 导入收集的数据并分析性能数据

帮助消除或减少等待的参考

每种等待类型的原因和解决方法各不相同。 没有一种常规方法来解析所有等待类型。 下面是排查和解决常见等待类型问题的文章:

有关许多等待类型及其指示的说明,请参阅“等待类型”中的表。

诊断查询计划差异

下面是查询计划差异的一些常见原因:

  • 数据大小或数据值差异

    是否在两台服务器上使用相同的数据库 - 使用相同的数据库备份? 与另一台服务器相比,数据是否已修改? 数据差异可能会导致不同的查询计划。 例如,将表 T1(1000 行)与表 T2(2,000,000 行)联接与联接表 T1(100 行)与表 T2(2,000,000 行)不同。 操作的类型和速度 JOIN 可能大相径庭。

  • 统计信息差异

    是否已 更新一个数据库而不是另一个数据库上的统计信息 ? 统计信息是否已使用不同的采样率进行更新(例如,30% 与 100% 完全扫描)? 确保使用相同的采样率更新两端的统计信息。

  • 数据库兼容性级别差异

    检查两个服务器之间的数据库兼容性级别是否不同。 若要获取数据库兼容性级别,请运行以下查询:

    SELECT name, compatibility_level
    FROM sys.databases
    WHERE name = '<YourDatabase>'
    
  • 服务器版本/生成差异

    两个服务器之间的 SQL Server 版本或内部版本是否不同? 例如,一台服务器 SQL Server 版本 2014 和另一个 SQL Server 版本 2016 吗? 可能存在产品更改,这可能会导致查询计划选择方式的变化。 请确保比较相同的 SQL Server 版本和版本。

    SELECT ServerProperty('ProductVersion')
    
  • 基数估算器 (CE) 版本差异

    检查旧基数估算器是否在数据库级别激活。 有关 CE 的详细信息,请参阅基数估计 (SQL Server)。

    SELECT name, value, is_value_default
    FROM sys.database_scoped_configurations
    WHERE name = 'LEGACY_CARDINALITY_ESTIMATION'
    
  • 已启用/禁用优化器修补程序

    如果在一台服务器上启用了查询优化器修补程序,但在另一台服务器上禁用,则可以生成不同的查询计划。 有关详细信息,请参阅 SQL Server 查询优化器修补程序跟踪标志 4199 服务模型

    若要获取查询优化器修补程序的状态,请运行以下查询:

    -- Check at server level for TF 4199
    DBCC TRACESTATUS (-1)
    -- Check at database level
    USE <YourDatabase>
    SELECT name, value, is_value_default 
    FROM sys.database_scoped_configurations
    WHERE name = 'QUERY_OPTIMIZER_HOTFIXES'
    
  • 跟踪标志差异

    某些跟踪标志会影响查询计划选择。 检查是否在一台服务器上启用了跟踪标志,另一台服务器上是否启用了跟踪标志。 在两台服务器上运行以下查询并比较结果:

    -- Check at server level for trace flags
    DBCC TRACESTATUS (-1)
    
  • 硬件差异(CPU 计数、内存大小)

    若要获取硬件信息,请运行以下查询:

    SELECT cpu_count, physical_memory_kb/1024/1024 PhysicalMemory_GB 
    FROM sys.dm_os_sys_info
    
  • 根据查询优化器的硬件差异

    OptimizerHardwareDependentProperties检查查询计划,看看硬件差异是否被认为对不同的计划很重要。

    WITH xmlnamespaces(DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
    SELECT
      txt.text,
      t.OptHardw.value('@EstimatedAvailableMemoryGrant', 'INT') AS EstimatedAvailableMemoryGrant , 
      t.OptHardw.value('@EstimatedPagesCached', 'INT') AS EstimatedPagesCached, 
      t.OptHardw.value('@EstimatedAvailableDegreeOfParallelism', 'INT') AS EstimatedAvailDegreeOfParallelism,
      t.OptHardw.value('@MaxCompileMemory', 'INT') AS MaxCompileMemory
    FROM sys.dm_exec_cached_plans AS cp
    CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp
    CROSS APPLY qp.query_plan.nodes('//OptimizerHardwareDependentProperties') AS t(OptHardw)
    CROSS APPLY sys.dm_exec_sql_text (CP.plan_handle) txt
    WHERE text Like '%<Part of Your Query>%'
    
  • 优化器超时

    是否存在 优化器超时 问题? 如果正在执行的查询过于复杂,查询优化器可以停止评估计划选项。 停止时,它会选取当时成本最低的计划。 这可能会导致一台服务器与另一台服务器上的任意计划选择。

  • SET 选项

    某些 SET 选项会影响计划,例如 SET ARITHABORT。 有关详细信息,请参阅 SET 选项

  • 查询提示差异

    一个查询是否使用 查询提示,另一个查询不使用查询提示 ? 手动检查查询文本以建立查询提示的存在。

  • 参数敏感计划(参数探查问题)

    是否使用完全相同的参数值测试查询? 如果没有,则可以从那里开始。 计划之前是否已基于不同的参数值在一台服务器上编译? 使用 RECOMPILE 查询提示测试这两个查询,以确保不会重复使用计划。 有关详细信息,请参阅调查并解决参数敏感问题

  • 不同的数据库选项/范围配置设置

    这两个服务器上是否使用相同的数据库选项或作用域配置设置? 某些数据库选项可能会影响计划选择。 例如,数据库兼容性、旧 CE 与默认 CE 以及参数探查。 从一台服务器运行以下查询,以比较两台服务器上使用的数据库选项:

    -- On Server1 add a linked server to Server2 
    EXEC master.dbo.sp_addlinkedserver @server = N'Server2', @srvproduct=N'SQL Server'
    
    -- Run a join between the two servers to compare settings side by side
    SELECT 
       s1.name AS srv1_config_name, 
       s2.name AS srv2_config_name,
       s1.value_in_use AS srv1_value_in_use, 
       s2.value_in_use AS srv2_value_in_use, 
       Variance = CASE WHEN ISNULL(s1.value_in_use, '##') != ISNULL(s2.value_in_use,'##') THEN 'Different' ELSE '' END
    FROM sys.configurations s1 
    FULL OUTER JOIN [server2].master.sys.configurations s2 ON s1.name = s2.name
    
    
    SELECT 
       s1.name AS srv1_config_name,
       s2.name AS srv2_config_name,
       s1.value srv1_value_in_use,
       s2.value srv2_value_in_use,
       s1.is_value_default,
       s2.is_value_default,
       Variance = CASE WHEN ISNULL(s1.value, '##') != ISNULL(s2.value, '##') THEN 'Different' ELSE '' END
    FROM sys.database_scoped_configurations s1
    FULL OUTER JOIN [server2].master.sys.database_scoped_configurations s2 ON s1.name = s2.name
    
  • 计划指南

    是否有任何计划指南用于一台服务器上的查询,但不用于另一个服务器上的查询? 运行以下查询来建立差异:

    SELECT * FROM sys.plan_guides