我相信,如果您有很多大型查询计划正在争夺内存以进行编译(这与运行查询本身无关,那么您会看到这种现象)。为此,我怀疑您正在使用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');
注意大多数这些指标都没有魔力“哦,天哪,我需要惊慌或做点什么!” 阈。您需要做的是在正常系统活动期间进行测量,并确定这些阈值对您的硬件,配置和工作负载的影响。当您惊慌地做某事时,必须满足两个条件:
- 指标与正常值差异很大;和,
- 实际上会发生性能问题(例如CPU峰值),但前提是它们确实在干扰任何事物。除了看到CPU峰值以外,您还看到其他症状吗?换句话说,尖峰是症状,还是尖峰引起其他症状?该系统的用户会注意到吗?很多人总是追求最高的等待消费者,仅仅是因为它是最高的。某些事物总是会成为等待时间最长的消费者-您必须知道它与正常活动之间的差异足以表明存在问题或某些重大更改。
Optimize for ad hoc workloads
可以解决99%的工作负载,但对降低编译成本不是很有帮助-它旨在通过防止一次性使用的计划在整个计划执行两次之前存储整个计划来减少计划缓存膨胀。即使仅将存根存储在计划缓存中,您仍然必须编译完整计划以执行查询。也许@Kahn的建议是将数据库级别的参数化设置为force ,这可能会提供更好的计划重用性(但这实际上取决于所有这些高成本查询的独特性)。
本白皮书还提供了一些有关计划缓存和编译的良好信息。
Optimize for ad hoc workloads
正如您所提到的,我们目前已经有了一套,与这个特定问题并没有真正的关系。我们确实有生成大量独特查询的代码,其中一些查询来自ORM工具,有些则经过手工编码。据我所知,CPU尖峰发生的时间不足以使我们的用户注意到。将数据库设置为强制参数设置对我来说听起来很危险。