间歇性RESOURCE_SEMAPHORE_QUERY_COMPILE等待统计


8

我正在尝试解决一些生产SQL Server上出现的间歇性CPU高峰问题。我们正在运行具有28 GB RAM和4个CPU内核的SQL Server 2008 R2标准版。发生这种情况时,我们注意到大量的RESOURCE_SEMAPHORE_QUERY_COMPILER等待,持续约一两分钟,然后停止,然后CPU使用率恢复正常。

经过研究后,我了解到这通常是由于编译了许多不可重用的执行计划而引起的,我们目前正在对应用程序进行更改以解决该问题。

由于内存压力,计划缓存逐出也会触发此行为吗?如果是这样,我将如何检查?我正在尝试查看是否有可以做的短期补救措施,例如升级服务器RAM,直到我们部署应用程序修补程序为止。我能想到的唯一其他短期选择是将某些最繁忙的数据库移至其他服务器。

Answers:


6

我相信,如果您有很多大型查询计划正在争夺内存以进行编译(这与运行查询本身无关,那么您会看到这种现象)。为此,我怀疑您正在使用ORM或某种会生成许多唯一但相对复杂的查询的应用程序。SQL Server可能会因为诸如大型查询操作之类的事务而承受内存压力,但进一步考虑到,您的系统配置的内存可能远远少于其所需的内存(或者永远没有足够的内存来满足您的所有查询)正在尝试编译,或者包装盒上有其他进程正在从SQL Server窃取内存)。

您可以使用以下方法查看配置了哪些SQL Server:

EXEC sp_configure 'max server memory';    -- max configured in MB

SELECT counter_name, cntr_value
  FROM sys.dm_os_performance_counters
  WHERE counter_name IN
  (
    'Total Server Memory (KB)',    -- max currently granted
    'Target Server Memory (KB)'    -- how much SQL Server wished it had
  );

您可以通过以下略微修改的Jonathan Kehayias查询来标识需要最多编译内存的缓存计划:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

;WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT TOP (10) CompileTime_ms, CompileCPU_ms, CompileMemory_KB,
  qs.execution_count,
  qs.total_elapsed_time/1000.0 AS duration_ms,
  qs.total_worker_time/1000.0 as cputime_ms,
  (qs.total_elapsed_time/qs.execution_count)/1000.0 AS avg_duration_ms,
  (qs.total_worker_time/qs.execution_count)/1000.0 AS avg_cputime_ms,
  qs.max_elapsed_time/1000.0 AS max_duration_ms,
  qs.max_worker_time/1000.0 AS max_cputime_ms,
  SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1,
    (CASE qs.statement_end_offset
      WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset
     END - qs.statement_start_offset) / 2 + 1) AS StmtText,
  query_hash, query_plan_hash
FROM
(
  SELECT 
    c.value('xs:hexBinary(substring((@QueryHash)[1],3))', 'varbinary(max)') AS QueryHash,
    c.value('xs:hexBinary(substring((@QueryPlanHash)[1],3))', 'varbinary(max)') AS QueryPlanHash,
    c.value('(QueryPlan/@CompileTime)[1]', 'int') AS CompileTime_ms,
    c.value('(QueryPlan/@CompileCPU)[1]', 'int') AS CompileCPU_ms,
    c.value('(QueryPlan/@CompileMemory)[1]', 'int') AS CompileMemory_KB,
    qp.query_plan
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('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS n(c)
) AS tab
JOIN sys.dm_exec_query_stats AS qs ON tab.QueryHash = qs.query_hash
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
ORDER BY CompileMemory_KB DESC
OPTION (RECOMPILE, MAXDOP 1);

您可以通过以下方式查看计划缓存的使用方式:

SELECT objtype, cacheobjtype,
    AVG(size_in_bytes*1.0)/1024.0/1024.0,
    MAX(size_in_bytes)/1024.0/1024.0,
    SUM(size_in_bytes)/1024.0/1024.0,
    COUNT(*)
FROM sys.dm_exec_cached_plans
GROUP BY GROUPING SETS ((),(objtype, cacheobjtype))
ORDER BY objtype, cacheobjtype;

当您遇到高信号量等待时,请检查这些查询结果是否与“正常”活动期间有显着差异:

SELECT resource_semaphore_id, -- 0 = regular, 1 = "small query"
  pool_id,
  available_memory_kb,
  total_memory_kb,
  target_memory_kb
FROM sys.dm_exec_query_resource_semaphores;

SELECT StmtText = SUBSTRING(st.[text], (qs.statement_start_offset / 2) + 1,
        (CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset
         END - qs.statement_start_offset) / 2 + 1),
  r.start_time, r.[status], DB_NAME(r.database_id), r.wait_type, 
  r.last_wait_type, r.total_elapsed_time, r.granted_query_memory,
  m.requested_memory_kb, m.granted_memory_kb, m.required_memory_kb,
  m.used_memory_kb
FROM sys.dm_exec_requests AS r
INNER JOIN sys.dm_exec_query_stats AS qs
ON r.plan_handle = qs.plan_handle
INNER JOIN sys.dm_exec_query_memory_grants AS m
ON r.request_id = m.request_id
AND r.plan_handle = m.plan_handle
CROSS APPLY sys.dm_exec_sql_text(r.plan_handle) AS st;

您可能还需要查看并查看内存的分配方式:

DBCC MEMORYSTATUS;

这里有一些很好的信息,说明为什么您可能会看到大量的编译/重新编译(这将导致等待时间增加):

http://technet.microsoft.com/en-us/library/ee343986(v=sql.100).aspx

http://technet.microsoft.com/en-us/library/cc293620.aspx

您可以使用以下计数器检查较高的编译/重新编译计数:

SELECT counter_name, cntr_value
  FROM sys.dm_os_performance_counters
  WHERE counter_name IN 
  (
    'SQL Compilations/sec',
    'SQL Re-Compilations/sec'
  );

而且,您可以检查导致驱逐的内部内存压力-这里的非零计数器表示计划缓存正在发生不好的事情:

SELECT * FROM sys.dm_os_memory_cache_clock_hands 
  WHERE [type] IN (N'CACHESTORE_SQLCP', N'CACHESTORE_OBJCP');

注意大多数这些指标都没有魔力“哦,天哪,我需要惊慌或做点什么!” 阈。您需要做的是在正常系统活动期间进行测量,并确定这些阈值对您的硬件,配置和工作负载的影响。当您惊慌地做某事时,必须满足两个条件:

  1. 指标与正常值差异很大;和,
  2. 实际上会发生性能问题(例如CPU峰值),但前提是它们确实在干扰任何事物。除了看到CPU峰值以外,您还看到其他症状吗?换句话说,尖峰是症状,还是尖峰引起其他症状?该系统的用户会注意到吗?很多人总是追求最高的等待消费者,仅仅是因为它是最高的。某些事物总是会成为等待时间最长的消费者-您必须知道它与正常活动之间的差异足以表明存在问题或某些重大更改。

Optimize for ad hoc workloads可以解决99%的工作负载,但对降低编译成本不是很有帮助-它旨在通过防止一次性使用的计划在整个计划执行两次之前存储整个计划来减少计划缓存膨胀。即使仅将存根存储在计划缓存中,您仍然必须编译完整计划以执行查询。也许@Kahn的建议是将数据库级别的参数化设置为force ,这可能会提供更好的计划重用性(但这实际上取决于所有这些高成本查询的独特性)。

本白皮书还提供了一些有关计划缓存和编译的良好信息。


Optimize for ad hoc workloads正如您所提到的,我们目前已经有了一套,与这个特定问题并没有真正的关系。我们确实有生成大量独特查询的代码,其中一些查询来自ORM工具,有些则经过手工编码。据我所知,CPU尖峰发生的时间不足以使我们的用户注意到。将数据库设置为强制参数设置对我来说听起来很危险。
DanM 2014年

一个问题-当您检查较高的汇编时,实际上构成较高的数字是什么?我认为每秒编译数仅与每秒批处理请求数相比才有意义。
DanM 2014年

就像我上面说的那样,@ DanM无法让我知道对您的环境可能有什么高处,因为我不知道对您的环境而言是什么正常。如果该数量接近或大于每秒批处理请求的数量,则可能是一个指标,但还是要视情况而定。例如,如果您的批处理包含5000条语句,并且其中10条需要重新编译(因为这可能在语句级别发生),则comp / sec将是批处理/ sec的10倍。那是问题吗?
亚伦·伯特兰

@DanM同样,您应该建议任何更改数据库或全局设置的建议,但默示的声明是您应该测试的内容,而不仅仅是启用它,因为Internet上有人这样做了。:-)我试图解释变更如何以及为什么会有所帮助,但我并不总是记得说出显而易见的内容:首先进行测试
亚伦·伯特兰

我明白您的意思-构成“高级”编译的内容完全取决于环境。
DanM 2014年

-1

到目前为止,我看到这些等待出现的最典型原因是索引分散或不足,以及样本数量不足或过时的统计信息。这会导致大量的全表扫描占用所有内存,进而产生一种症状,我们经常将其视为RESOURCE_SEMAPHORE_QUERY_COMPILE。

验证此问题最简单的方法是检查查询何时应执行索引查找,是否运行全表扫描/索引扫描。如果您有问题查询可用来重现问题-诊断和解决问题变得非常容易。

我会检查那些问题查询影响的表上的索引-即。检查索引碎片,未使用的潜在过滤索引,可能要创建的缺失索引等。此外,请尽快使用FULLSCAN更新其统计信息。

要记住的一个好点是问题表可能不是唯一需要此表的人。例如,如果您有一个查询,该查询从10个表中获取数据,则执行计划程序可能偶尔会显示它没有使用表1上的索引,但是当您随后检查表1上的索引时,实际上是可以的。查询计划者可能会决定正确地使用全表扫描来获取表1上的数据,因为例如表7上的索引错误/不足,返回了太多数据,因此这将是更快的选择。因此,诊断这些有时可能很棘手。

另外,如果您有很多代码隐藏查询,而变量值仅作了一些更改,则可能需要考虑为临时工作负载启用优化。基本上,它的工作是存储已编译计划的存根而不是整个计划,以在每次都无法获得完全相同的计划时节省资源。


您突出显示的大多数内容都会导致查询计划效率低下,而不是很高的编译时间。恕我直言。
亚伦·伯特兰

但是,我已经看到这种情况发生了好几次,而等待的时间通常正是随后发生的情况。显然,我不知道它有多普遍或是否适用于此,但是上面提到的方法已将其修复。
卡恩2014年

作为后期编辑,在我们的案例中,在关键索引/统计信息受到影响之后,它一直出现在一个相当大的数据库中,该索引/统计信息一直被大量正在运行的查询所使用。
卡恩2014年

1
是的,编译等待的到来是因为索引/统计已更改,这导致所有相关计划都需要重新编译,而不是因为它们零散或过时(例如您的回答状态)。
亚伦·伯特兰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.