部分内容借用《SQL Server 2012实施与管理实战指南》P592,如果SQL Server错误日志里面并没有17883/17884这类错误,但是SQL Server CPU很高,那一般就是工作负载太高导致的。意味着SQL Server并没有什么大问题,但是很辛苦地在完成用户发过来的请求。
由于CPU较于内存、阻塞、磁盘等资源瓶颈而言相对较后,所以SQL Server的CPU使用率一般不会太高,如果60~70%就已经算是很高了。那么一般集中使用CPU的资源有那些?
一般要在SQL Server配置上能做手脚的根据个人经验,主要是Max Degree of Parallelism和Cost Threshold of Parallelism。
可以使用计数器,也可以使用代码做一个粗略的计算获取实例性能概要信息。计数器可以考虑:
这个其实有点难度,除非周期性发生。虽然有一系列的DMV可以使用,但是由于驻留内存的特性,可能会在动手之前已经重启服务器导致信息丢失。而开启sql trace又容易加大负担。这个时候,可以考虑使用扩展事件配合周期性运行查询DMV的语句并存储在实体表中的方式。后者使用类似Zabbix这类第三方监控软件。下面一些语句可供参考:
扩展事件:GetWasteQueries
DMV:DMVs_CpuRelated/RunningSessionInfos
如果在优化工作之后,确定是硬件瓶颈,那升级硬件也是不错的选择。
过度编译和重编译通常可以对语句进行参数化,使用下面语句检查是否适合进行参数化,
SELECT q.query_hash
,q.number_of_entries
,q.distinct_plans
,t.TEXT AS sample_query
,p.query_plan AS sample_plan
FROM (
SELECT TOP 20 query_hash
,count(*) AS number_of_entries
,count(DISTINCT query_plan_hash) AS distinct_plans
,min(sql_handle) AS sample_sql_handle
,min(plan_handle) AS sample_plan_handle
FROM sys.dm_exec_query_stats
GROUP BY query_hash
HAVING count(*) > 1
ORDER BY count(*) DESC
) AS q
CROSS APPLY sys.dm_exec_sql_text(q.sample_sql_handle) AS t
CROSS APPLY sys.dm_exec_query_plan(q.sample_plan_handle) AS p
GO
结果类似下图:
如果number_of_entries量很大,同时distinct_plans为1,那么这些很适合进行参数化。另外需要注意的是最佳的参数化是在客户端。
通常关注下面的计数器:一般来说,对于OLTP而言, SQL Recompilations/sec 相对于Batch Requests/sec应该很小。
SQL Server: SQL Statistics: Batch Requests/sec
SQL Server: SQL Statistics: SQL Compilations/sec
SQL Server: SQL Statistics: SQL Recompilations/sec
另外使用下面语句也可以查到缓存中前25个被重编译的语句。plan_generation_num代表查询发生重编译的次数。
SELECT TOP 25 sql_text.TEXT
,sql_handle
,plan_generation_num
,execution_count
,dbid
,objectid
FROM sys.dm_exec_query_stats a
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sql_text
WHERE plan_generation_num > 1
ORDER BY plan_generation_num DESC
结果类似:
如上图的最后一行就是业务语句,而IndexOptimize是运维脚本。那么不妨检查最后一行。对于重编译,如果真有问题,那么我们通常要做的是:
常见的CPU消耗操作有Hash和Sort操作。可以参考:SQL Server Table Spool优化 和 SQL Server Hash Warning 优化。另外可以使用下面DMV来查询累计消耗CPU最多的查询:
SELECT highest_cpu_queries.plan_handle
,highest_cpu_queries.total_worker_time
,q.dbid
,q.objectid
,q.number
,q.encrypted
,q.[text]
FROM (
SELECT TOP 50 qs.plan_handle
,qs.total_worker_time
FROM sys.dm_exec_query_stats qs
ORDER BY qs.total_worker_time DESC
) AS highest_cpu_queries
CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS q
ORDER BY highest_cpu_queries.total_worker_time DESC
可以改写查询,添加sys.dm_exec_cached_plans用于过滤'%Hash Match%'和'%sort%'来精确定位。也可以使用这里的代码来持续监控: HashandSortWarningwithxEvents
当找到问题之后,一般需要做的操作有:
语句中的并行操作很多时候也会导致CPU过高,如果没有控制,而某个语句可能运行时占据所有CPU,这时候其他操作全部等待这个语句运行完毕才能继续操作,影响整个系统的响应速度。对于正在运行的服务器,使用下面语句来查看是否有正在进行并行操作的语句:
SELECT r.session_id
,r.request_id
,max(isnull(exec_context_id, 0)) AS number_of_workers
,r.sql_handle
,r.statement_start_offset
,r.statement_end_offset
,r.plan_handle
FROM sys.dm_exec_requests r
JOIN sys.dm_os_tasks t ON r.session_id = t.session_id
JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id
WHERE s.is_user_process = 0x1
GROUP BY r.session_id
,r.request_id
,r.sql_handle
,r.plan_handle
,r.statement_start_offset
,r.statement_end_offset
HAVING max(isnull(exec_context_id, 0)) > 0
还可以查看符合并行操作的语句:
SELECT p.*
,q.*
,cp.plan_handle
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) p
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS q
WHERE cp.cacheobjtype = 'Compiled Plan'
AND p.query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";max(//p:RelOp/@Parallel)','float') > 0
如果发现存在,可以考虑如下应对措施:
前面是几十年来大量专家总结的经验,在没有头绪的时候不妨参考一下。但是我建议大家一定要有几个潜意识: