SQL Server 2008性能故障排查(二)——CPU

CPU 瓶颈:
CPU瓶颈可能因为某个负载所需的硬件资源不足而引起。但是过多的CPU使用通常可以通过查询优化(特别是在一个服务器上没有增加额外负载或不同查询时CPU突然上升的情况)、寻找应用程序设计方面的问题和优化系统配置来减少。 在你急于买更快、更多的CPU之前,先要找出最耗费CPU资源的查询,并且尝试去调整那些查询或者调整设计/系统配置因素。
性能监视器是用于监测和识别CPU是否已经成为了性能瓶颈的其中一个有用的工具。你要查看:Processor:%Processor Time计数器是否很高。如果每个CPU的该值持续在80%以上,那么意味着CPU已经成为瓶颈。
通过SQL Server你也可以通过DMVs检查CPU是否存在瓶颈。如果在请求等待类型中发现有SOS_SCHEDULER_YIELD等待类型或者一个高值的runnable任务都可是提示可运行线程在计划等待中。这意味着处理器上出现了CPU瓶颈。如果你有可用的数据收集器,SQLServer的等待图表可以很容易地在任何时候查看到现有活动中的是否有CPU瓶颈。消耗CPU和SOS_SCHEDULER_YIELD等待都在报表中被纳入CPU等待范畴。当你看到CPU在高比例使用时,你可以深入查找那些耗资源最多的查询。
下面的查询为你提供一个高级视图去查找目前缓存批处理或者存储过程中使用做多CPU资源的查询。这个查询聚合计算所有执行相同计划句柄(Plan handle意味着他们来自相同批处理或者存储过程)CPU消耗。如果计划句柄超过一个语句,你就必须去深入分析以便找到在整个CPU中耗费最大的那个特定查询:
select top 50   
    sum(qs.total_worker_time) as total_cpu_time,   
    sum(qs.execution_count) as total_execution_count,  
    count(*) as  number_of_statements,   
    qs.plan_handle   
from   
    sys.dm_exec_query_stats qs  
group by qs.plan_handle  
order by sum(qs.total_worker_time) desc  
这节剩余部分将讨论一下通过SQL Server和其他一些有效的方法来增强CPU以解决这些问题。
过度的查询编译和优化:

查询编译和优化是一个高CPU消耗的过程。开销根据查询的复杂度和优先计划增加而增加。但是即使一个简单的查询也会耗用10-20毫秒的CPUT时间去解析和编译。
为了检查这种开销,SQLServer缓存并重用经过编译的查询计划。每次接收到来自客户端的查询时,SQLServer首先回去查找计划缓存,是否已经存在一个可以重复使用的编译计划。如果找不到适合的计划,SQLServer将对查询进行分合和编译。然后再执行。
• 对于OLTP系统。批处理的提交相对来说是小而固定的。一般情况下最佳的执行计划不会依赖于某些值或者作为谓词的值,因为查询是基于键值的。重用执行计划在这种类型的系统中非常重要,因为编译的开销往往接近甚至高于直接运行的开销。但是,对于一个数据仓库负载将能从专用SQL和允许查询优化器根据不同的值选择不同的执行计划中得到好处。因为运行这些查询的时间通常比编译时间要大得多。并且查询优化计划经常根据查询谓词而改变。使用参数化查询或者存储过程对OLTP系统能充分重用已经编译的执行计划。从而降低SQLServer对CPU的耗用。你可以在数据库或者查询级别上使用PARAMETERIZATION FORCED数据库选项或者查询提示来实现参数化。更多关于该特性使用的限制,比如当你依赖于计算列的索引或者索引视图等,请查看联机丛书。但是,参数化最好的使用地方还是在应用程序自己内部。同时能通过参数化减少被SQL注入的机会。相关只是可以查看联机丛书部分:
• SQL Injection (http://msdn.microsoft.com/en-us/library/ms161953.aspx)
• Using sp_executesql (http://msdn.microsoft.com/en-us/library/ms175170.aspx)


侦测问题:

在编译过程中,SQLServer2008计算查询的“签名”并作为sys.dm_exec_requests和sys.dm_exec_query_stats动态视图中的query_hash列的信息展示。这个queryhash属性在showplan/statistics xml实体中对具有相同query_hash值的高可能行设计相同查询文本,如果它被写成一个query_hash参数化形式。查询中仅是字面值不同但拥有相同的值。举个例子:有两个共享相同query hash的查询,当第三个查询有不同的query hash时。由于它执行不同的操作:
select * from sys.objects where object_id = 100  
select * from sys.objects where object_id = 101  
select * from sys.objects where name = 'sysobjects'  

query hash将在编译阶段从产生结构中被重新计算。空格将被忽略,就像在SELECT 中,显式指定列和使用*号的query hash是不一样的。此外,在一个查询中使用完全限定名和另外一个使用全表名作为前缀的查询被认为是同一个查询,具有相同的query_hash:
[sql] view plain copy
 print?
Use AdventureWorks  
Go  
  
set showplan_xml on  
go  
  
-- Assume this is run by a user whose default schema is Sales  
select * from SalesOrderHeader h  
  
select * from Sales.SalesOrderHeader h  
  
select SalesOrderID,  
    RevisionNumber,  
    OrderDate,  
    DueDate,  
    ShipDate,  
    Status,  
    OnlineOrderFlag,  
    SalesOrderNumber,  
    PurchaseOrderNumber,  
    AccountNumber,  
    CustomerID,  
    ContactID,  
    SalesPersonID,  
    TerritoryID,  
    BillToAddressID,  
    ShipToAddressID,  
    ShipMethodID,  
    CreditCardID,  
    CreditCardApprovalCode,  
    CurrencyRateID,  
    SubTotal,  
    TaxAmt,  
    Freight,  
    TotalDue,  
    Comment,  
    rowguid,  
    ModifiedDate  
from Sales.SalesOrderHeader h  
go  
set showplan_xml off  
go  


注意当query_hash值产生时,数据库部分的完全限定名被忽略。这允许在相同实例的很多数据库中执行查询而不至于因为指定了数据库前缀使得查询不成功。
一个简单的识别应用程序提交的特殊查询是查看sys.dm_exec_query_stats.query_hash列:
[sql] view plain copy
 print?
select q.query_hash,   
    q.number_of_entries,   
    t.text as sample_query,   
    p.query_plan as sample_plan  
from (select top 20 query_hash,   
            count(*) as number_of_entries,   
            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值在千百级别意味着参数化很优秀。如果你在XML执行计划的<QueryPlan>标签中查看CompileTime和CompileCPU属性值是number_of_entries值的翻倍,你应该预测到多少编译时间和CPU用于参数化查询(意味着查询只编译了一次但是被重用了多次)。去除不必要的缓存计划还有一个难懂的好处。释放内存中的缓存用于其他编译计划,并且留下更多内存给缓存。

解决方案:
SQLServer2008同时也在执行计划的访问路径(即join算法、join顺序、索引选择等等)产生一个名为“签名”的query_plan_hash值。某些应用程序通过判断传入查询的参数来评估优化器给出的不同执行计划。在这种情况下如果你不想使得查询参数化,你可以使用query_hash和query_plan_hash值共同决定一个具有相同query_hash值的特定的查询结果是否拥有相同或不同的query_plan_hash值,或者访问路径。在之前提到的查询中稍作修改:
[sql] view plain copy
 print?
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  


注意本查询根据给定的query_hash值返回一个唯一的查询计划(query_plan_hash值)。即使唯一的计划数量超过1个,你也可以使用sys.dm_exec_query_plan去检索不同的执行计划和检验它们对实现性能优化是否不同。
当你决定哪个查询需要被参数化时,参数化的发生地方最好是在客户端应用程序。具体的实现方法受到你所选择的API。但是有一件不变的事实就是所有API都用于替代创建文本化谓词,你可以创建一个带有问号(?)的字符串作为参数占位符。
[sql] view plain copy
 print?
-- Submitting as ad hoc query  
select * from Sales.SalesOrderHeader where SalesOrderID = 100  
  
-- Submitting as parameterized  
select * from Sales.SalesOrderHeader where SalesOrderID = ?  

你应该为你绑定参数值使用一个合适的APIs(ODBC,OLE DB,或者SQL Client)。客户端使用sp_executesql来执行参数化:
exec sp_executesql N’select * from Sales.SalesOrderHeader where SalesOrderID = @P1’, N’@P1 int’, 100
由于查询是参数化,能重用现有的缓存计划。如果希望整个数据库合理地参数化,并且你不需要控制或者修改客户端应用程序,你同样可以使用强制数据库参数来实现。注意前面的警告,这会阻止优化器选择有效的索引视图和索引:会阻止优化器选择有效的索引视图和索引:会阻止优化器选择有效的索引视图和索引:
ALTER DATABASE AdventureWorks SET PARAMETERIZATION FORCED
如果你不能在客户端应用程序参数化或者强制整个数据库参数化,你依然能对某些查询使用临时的计划暗示,使用OPTION(PARAMETERIZATION FORCED)。

非必要的重编译:
当一个批处理或者一个远程存储调用(RPC)被提交到SQLServer时,服务器会在开始执行钱检查查询计划的有效性和正确性。如果这些检查不通过,批处理就必须重新编译并产生一个不同的执行计划。这种编译叫“重编译”。重编译很多时候是必须的,用于确保正确性或者当服务器觉得底层数据更改时重编译更加有效。编译是一件耗费CPU资源的动作,因此过度的重编译结果可能是影响系统的CPU性能。
在SQLServer2000中,当SQLServer重编译一个存储过程时,整个存储过程都会被重编译。在SQLServer2005、2008中,存储过程的重编译是语句级别的。当2005、2008重编译存储过程时。只重编译需要编译的语句,而不是整个存储过程编译。这样能减少CPU频宽和更少的锁资源(比如COMPILE锁)。重编译一般出现在以下情况:
 架构变更。
统计信息变更
延迟编译
SET选项更改
临时表变更
在存储过程创建时使用了RECOMPILE提示。

探测:
可以使用性能监视器和SQLServer Profiler去探测过多的编译和重编译:
性能监视器(Performance Monitor):
SQL Statistics对象提供编译监视计数器和SQLServer实例被发送请求的类型。你需要监控批处理的编译和重编译数来发现哪些制造了高CPU使用率。理想情况下,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
详细信息请参考联机丛书中的SQL Statistics Object
SQL Server Profiler Trace:
如果性能监视器暗示重编译次数过高,重编译就有可能成为SQLServer耗费CPU资源的罪魁祸首。可以通过Profiler来跟踪存储过程重编译情况。Profiler会连同重编译的原因也追踪出来:
SP:Recompile/SQL:StmtRecompile:前者是存储过程,后者是语句的编译。当你编译一个存储过程时,会产生这个存储过程及其每个语句的相应事件。但是当存储过程重编译时,只有引起重编译的语句会被重编译。更重要的数据列是SP:Recompile事件。EventSubClass数据列对于发现重编译的原因是非常重要的。SP:Recompile会在存储过程中触发一次或者重编译时触发一次,并且不对不重编译操作的特殊批处理不触发。在05、08中,更加游泳的是SQL:StmtRecomile。因为这个计数器会在所有重编译时触发。重要的事件列:
EventClass
EvnetSubClass
ObjectID(包含该语句的存储过程ID)
SPID
StartTime
SqlHandle
TextData
更多信息请查看联机丛书。
如果你已经有了一个跟踪文件,可以使用下面语句查看所有重编译事件:
[sql] view plain copy
 print?
select   
    spid,  
    StartTime,  
    Textdata,  
    EventSubclass,  
    ObjectID,  
    DatabaseID,  
    SQLHandle   
from   
    fn_trace_gettable ( 'e:
ecompiletrace.trc' , 1)  
where   
    EventClass in(37,75,166)  

其中:EventClass 37 = Sp:Recompile, 75 = CursorRecompile, 166 = SQL:StmtRecompile
你也可以更进一步组织结果,把sqlhandle和ObjectID列分组或者更多的列。要查看是否存储过程有过多的重编译或者因为某种原因重编译(比如SET选项)
Showplan XML For Query Compile:
该事件会在SQLServer编译或者重编译一个T-SQL语句时触发。这个事件是关于语句编译或重编译的信息。包括执行计划、ObjectID。获取这个时间对总体性能是有意义的,因为它能捕获每一个编译或重编译。如果你在SQL Compilations/sec指针长期处于高值。就要注意这个事件了。可以通过这个时间查看什么语句经常重编译。然后通过改变语句的参数来降低重编译次数。
DMVs:
当你使用sys.dm_exec_query_optimizer_info DMV是,可以看到SQLServer在优化时间方面的一些好建议。如果你执行该DMV两次,你将感受到在给定时间内用于优化的时间:
select * from sys.dm_exec_query_optimizer_info
counter occurrence value 
---------------- -------------------- --------------------- 
optimizations 81 1.0
elapsed time 81 6.4547820702944486E-2
要重点关注elapsed time,因为当优化时,这个时间通常接近用于优化的CPU时间。因为优化处理是非常耗费CPU资源的。你可以看到那些编译和重编译操作时最好CPU资源的。
另外一个有用的DMV是:sys.dm_exec_query_stats,主要关注列有:
• Sql_handle
• Total worker time
• Plan generation number
• Statement Start Offset
特别关注Plan_generation_num,因为是查询被重编译的次数。下面是一个查询前25个重编译最多的存储过程信息:
[sql] view plain copy
 print?
select * from sys.dm_exec_query_optimizer_info  
  
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  



解决方案:
如果你发现了耗时的编译和重编译,可以考虑以下步骤:
 如果重编译的发生原因是因为SET选项,那么用SQLServer Profiler来抓去那个SET选项改变了,然后把它禁用掉(程序级别,不是数据库级别)。最好是把这个SET操作放到数据库连接级别,因为这能保证在连接的生命周期中有效。
 临时表的重编译临界值比实体表要低,如果因为临时表的统计信息改变而造成重编译,可以把临时表改成表变量。表变量在基数上的更改不引发重编译。但是这种方法的缺点是查询优化器不保持跟踪表变量的基数,因为表变量不产生统计信息和维护这些信息。这回导致执行计划性能优化方面的不完整。你可以测试各种方法然后选择最好的一种。
 另外一种选项是使用KEEP PLAN查询暗示(query hint)。这种设置使的临时表和实体表具有相同临界值。EventSubClass列显示在临时表上一个操作的统计信息改变情况。
 为了避免统计信息的改变而重编译(比如,如果执行计划因为数据的统计信息改变而变成次优级别时)。可以特别执行query hint。只当这个执行计划需要检查正确性时才发生重编译(比如底层结构更改或者执行计划长时间没有使用)。并且不依赖统计信息的改变。比如表架构的更改、或者使用了sp_recompile存储过程去标注表。
 关闭了自动更新统计信息能防止重编译。因为统计信息的改变也会导致重编译的发生。但是注意,关闭自动更新不是一个好的建议,因为查询优化器不在对对象上的数据更改敏感,会导致执行计划不是最优化。只是在使用了所有其他改变都无效时的不得已手段。
 数据库中的对象应该使用完全限定名,如dbo.table1,这样避免重编译和避免引起对象歧义。
 为了避免重编译,可以延迟编译,不要交错使用DML和DDL或者使用有条件的DDL比如IF语句。
 运行DTA看看是否可以通过调整索引来改善编译时间和执行时间。
 检查存储过程是否有WITH RECOMPILE的选项。或者是否在创建存储过程时有WITH RECOMPILE选项。在2005、2008中如果有需要,可以在语句级别加上RECOMPILE提示。在语句级别使用这个提示可以避免整个存储过程重编译。


低效查询计划:
当查询产生一个执行计划时,sqlser优化器会尝试选择响应时间最短的计划。最快响应时间并不一定意味着最小化I/O。或者最小化CPU,而是各种资源上的一个平衡。
某些操作与生俱来就比普通操作更占用CPU资源。如Hash操作和排序操作会分别扫描各自的输入数据。如果在类似扫描中【预读】,那么在缓存中的所需页面几乎总之可用的。因此,物理I/O的等待将最小化或者完全消失。如果这些类型的操作不再受制于物理I / O时,他们倾向于出现在高CPU的消耗。 相比之下,具有很多索引查找的嵌套循环关联如果需要跨越表的很多部分才能找到合适的数据,那么会很快成为I/O瓶颈。
查询优化器会对最有意义的输入,使用基于成本的各种方法来评估各种基数操作,以便找出最有效的查询计划。(即执行计划中的EstimateRows和EstimateExecutions属性)。没有明确的基数评估,那么执行计划是有缺陷的,并且往往这个却是最终方案。
关于描述SQLServer优化器所使用的统计信息,请查阅:Statistics Used by the Query Optimizer in Microsoft SQL Server 2005

侦查:
低效的执行计划相对来说比较容易发现,一个低效的执行计划能引起CPU的消耗上升。下面的查询是有效标识高消耗CPU的方法:
[sql] view plain copy
 print?
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来过滤可能引起CPU高消耗的各种操作,比如:’%Hash Match%’、’%Sort%’等。

解决方案:
如果你发现有不高效的执行计划,可以尝试以下方法:
 使用DTA优化程序,检查是否有索引问题。
 检查坏基数评估
 检查语句的where子句是否可以更好地筛选数据。没有严格限定的查询天生就很耗资源。
 在表上运行UPDATE STATISTICS检查是否还存在问题。
 检查是否使用了构造器使得优化器不能准确评估基数?考虑是否可以改写查询来避免这种情况?
 如果无法修改架构或者查询,你可以在查询中使用查询提示(query hints)来限定执行计划。计划向导同时也对存储过程中创建的特殊查询有效。Hints能脱离优化器对查询计划的潜在更改。
 SQLServer 2008 同样提供一个叫做【计划冻结】的新特性。允许你冻结在执行计划缓存中存在的特定计划。这个选项类似于在执行计划想到中使用USE PLAN查询提示来定义计划。但是,它消除了使用很长的命令来创建执行计划向导。并且能最小化在长于据中出现的用户错误。示例如下:
[sql] view plain copy
 print?
DECLARE @plan_handle varbinary(64);  
  
-- Extract the query's plan_handle.  
SELECT @plan_handle = plan_handle FROM sys.dm_exec_query_stats AS qs  
CROSS APPLY sys.dm_exec_sql_text(sql_handle)  
WHERE text LIKE N'Some query matching criteria%';  
  
EXECUTE sp_create_plan_guide_from_handle  
    @name =  N'Sample_PG1',  
    @plan_handle = @plan_handle,  
    @statement_start_offset = NULL;  
GO  



查询内并行:
当生成了执行计划以后,SQLServer优化器会尝试选择响应时间最短的计划来实现查询。如果查询消耗超过了并行度花销临界值,查询优化器会尝试生成一个计划来做并行运行。并行执行计划使用处理器的多个线程来处理查询。最大并行度可以在服务器级别做限制,使用max degree of parallelism选项。在资源负载组级别或者每个查询级别使用OPTION(MAXDOP)提示。实际执行的并行度(实际用于并行操作的标准)被延迟到执行时才实现。在执行之前,SQLServer会根据空闲程度决定可用于执行DOP的可用调度器。在DOP被选定以后,查询会在该并行度中执行直到结束。并行执行会稍微耗用更多的CPU时间,但只是持续一段很短的时间。当没有其他瓶颈时,比如物理I/O等待等,并行计划会完全使用100%的CPU。
一个关键的因素是在查询开始执行后,引导查询使用并行计划。但是这种情况在执行开始后还是可以变更的。比如,如果查询在空闲发起,服务器可能会选择使用使用4个处理器来并行执行。在这些线程开始执行,现有的连接可以提交其他查询这也需要大量的CPU。在那时,其他不同的线程会共享可用CPU的短时间片。这将导致高查询持续时间。
使用并行计划运行不是一件坏事,因为它能提供更快的响应时间。但是,对给定查询的响应时间需要权衡,应该从整体去权衡。并行查询适合批处理,并且会根据负载来选择运行环境。SQLServer2008对分区表查询具有很好的扩展性,所以SQLServer2008会在并行运行时使用比旧版本更高的CPU数量。如果这不是你想要的,就应该限制甚至禁用并行性。

侦查:
并行查询所带来的影响可以使用以下方法来侦测:
性能监视器(Performance Monitor):
重点关注:SQL Server:SQL Statistics – Batch Requests/sec计数器和SQL Statistics Objects。由于执行并行查询需要预估开销不能超过设定的阈值(默认为5,可以在配置里面设定),所以如果每秒服务器要处理的批处理过多,那么将只有少量的批处理以并行方式执行。服务器通常情况下会并行运行一些小批处理,比如100个以下。
DMVs:
从服务器运行开始,可以使用以下查询来监控:
[sql] view plain copy
 print?
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  

在结果中,可以使用sys.dm_exec_sql_text轻易找出查询内容,并使用sys.dm_exec_cached_plan来查看执行计划。
同时可以查找正在并行运行的执行计划,可以查看是否有Parallel操作符且其属性非0的执行计划。这些计划可能不是以并行方式运行,但是他们如果在系统不是非常繁忙的时候,还是能按并行方式运行
[sql] view plain copy
 print?
--  
-- Find query plans that can run in parallel  
--  
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  

一般情况下,一个查询的持续时间会比CPU时间更长,因为其中一些时间是花费在等待别的资源如锁或者物理I/O上。但在一种情况下查询的花费CPU时间会比持续时间更长,就是当查询以并行方式运行在目前可用的多个线程上。但是注意不是所有的并行查询都会以这种方式运行。
[sql] view plain copy
 print?
select   
    qs.sql_handle,   
    qs.statement_start_offset,   
    qs.statement_end_offset,   
    q.dbid,  
    q.objectid,  
    q.number,  
    q.encrypted,  
    q.text  
from   
    sys.dm_exec_query_stats qs  
    cross apply sys.dm_exec_sql_text(qs.plan_handle) as q  
where   
    qs.total_worker_time > qs.total_elapsed_time  
SQL Trace  
Look for the following signs of parallel queries, which could be either   
statements or batches that have CPU time greater than the duration.  
  
select   
    EventClass,   
    TextData   
from   
    ::fn_trace_gettable('c:	emphigh_cpu_trace.trc', default)  
where   
    EventClass in (10, 12)  -- RPC:Completed, SQL:BatchCompleted  
    and CPU > Duration/1000  -- CPU is in milliseconds, Duration in   
microseconds oOr can be Showplans (un-encoded) that have Parallelism   
operators in them  
select   
    EventClass,   
    TextData   
from   
    ::fn_trace_gettable('c:	emphigh_cpu_trace.trc', default)  
where   
    TextData LIKE '%Parallelism%'  



解决方法:
 以并行计划运行的查询,优化器会确认是否预计花销超过了默认阈值(5)。一些查询会被标记,作为候选优化方案。
 使用DTA查看是否有索引需要修改。改动索引能减少查询开销。
 检查预估执行计划和实际执行计划,因为预估是基于统计信息,而且基于成本。如果下面的情况出现了,就要检查问题:
o 是否禁用了自动开启统计,确保在执行计划的警告部分不会出现丢失状态等信息
o 如果预估计划被禁用,那么在对应表上运行:UPDATE STATISTICS
o 确保查询没有使用到优化器无法明确预估的构造器,如多语句的表值函数或者CLR函数、表变量或者带有比较功能的T-SQL函数(带有参数的比较是允许的)。
o 评估语句是否能用更有效更新的方式去实现。

低效的游标使用:

在2005之前的版本只支持单独的活动连接。正在被查询或者等待被发送到客户端的查询被认为是活动的。在某些情况下,客户端可能接收结果后,会把结果里面的语句返回SQLServer再继续执行,这样会引起新一轮的挂起。一个比较好的解决办法是改变连接属性,在服务器端使用游标。
当使用服务器端的游标时,数据库客户端软件(OLE DB或者ODBC)会透明地压缩客户端请求到一个特殊的扩展存储过程,如sp_cursoropen /sp_cursoffetch。这是引用一个API游标(于T-SQL游标相反)。当用户执行查询时,查询文本将通过sp_cursoropen被发送到服务器。请求将从sp_cursorfetch通过服务器返回的特定行中读取数据。为了控制数据行的返回,ODBC或者OLE DB可以使用行缓存。这避免了服务器等待客户端读取它返回的所有数据,这样,服务器就可以准备接收该连接的新请求。
应用程序打开游标开始读取时,容易由于网络延迟造成瓶颈。特别在广域网。在多用户连接的快速网络,从总体上进程请求很多游标带来的性能问题也会变得很明显。因为总体响应时间会因为游标定位每个结果集、预处理和类似处理。
侦测:

你可以使用下面工具来排查低效的游标应用:
性能监视器:
查看SQL Server: Cursor Manager By Type-Cursor Requests/Sec计数器。可以感受到大概游标使用情况。系统可能因为小型但每秒有几百个提取操作而导致存在高CPU利用。没有专门的计数器列举提取所使用的缓存大小。
DMVs:
可以使用下面的DMV来查询每个连接的API游标提取的缓存大小。
[sql] view plain copy
 print?
select   
    cur.*   
from   
    sys.dm_exec_connections con  
    cross apply sys.dm_exec_cursors(con.session_id) as cur  
where  
    cur.fetch_buffer_size = 1   
    and cur.properties LIKE 'API%'  -- API cursor (Transact-SQL cursors   
always   
have a fetch buffer of 1)  

SQL Trace:
使用包含RPC:Completed事件去查找sp_cursorfetch语句。第四个参数的只是提取操作所返回的行数。具体大小根据输出而定。可以看相应的RPC:Starting事件。

解决方法:
 检查是否可以使用基于集合的操作来替代游标,因为这样几乎一定比游标高效。
 考虑在连接SQLServer2008是,开启多活动结果(MARS)
 查阅你使用的API文档。决定如何定义一个对于游标的大的提取缓存:
o ODBC-SQL_ATTR_ROW_ARRAY_SIZE
o OLE DB –Irowset::GetNextRows 或者IRowsetLocate::GetRowsAt
原文地址:https://www.cnblogs.com/binghou/p/9109443.html