间歇性 RESOURCE_SEMAPHORE_QUERY_COMPILE 等待统计

Dan*_*anM 8 sql-server wait-types

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

对此进行研究后,我了解到这通常是由编译大量不可重用的执行计划引起的,我们目前正在对应用程序进行更改以解决这些问题。

由于内存压力,计划缓存逐出也可以触发这种行为吗?如果是这样,我将如何检查这个?我想看看我们是否可以采取任何短期补救措施,例如升级服务器 RAM,直到我们部署应用程序修复程序。我能想到的唯一其他短期选择是将一些最繁忙的数据库移动到不同的服务器。

Aar*_*and 7

我相信如果您有很多大型查询计划为了编译而争夺内存(这与运行查询本身几乎没有关系),您会看到这种症状。为了解决这个问题,我怀疑您正在使用 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
  );
Run Code Online (Sandbox Code Playgroud)

您可以使用以下略作修改的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);
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

您可能还想查看内存是如何分布的:

DBCC MEMORYSTATUS;
Run Code Online (Sandbox Code Playgroud)

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

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

http://technet.microsoft.com/en-us/library/cc293620.aspx
Run Code Online (Sandbox Code Playgroud)

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

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

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

SELECT * FROM sys.dm_os_memory_cache_clock_hands 
  WHERE [type] IN (N'CACHESTORE_SQLCP', N'CACHESTORE_OBJCP');
Run Code Online (Sandbox Code Playgroud)

注意这些指标中的大多数都没有魔法“哦,我的天哪,我需要恐慌或做点什么!” 临界点。您需要做的是在正常系统活动期间进行测量,并确定您的硬件、配置和工作负载的阈值在哪里。当你恐慌时做某事是当两个条件成立时:

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

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

本白皮书中还有一些关于计划缓存和编译的好信息。