4 .3 .4 常见高CPU利用率的缘由
存在髙CPU利用率的问题类型有不少种,可是咱们能够关注一些常见类型,至于其余
极端类型暂时不包含。如下即是高CPU利用率的常见类型:
□缺失索引(Missing Index)
□统计信息过期
□ 非 SARG查询
□ 隐式 转 换 (Implicit conversions □ 参数嗅探(Parameter sniffing)
□非参数化Ad-hoc査询 □非必要的并行查询
下面分别介绍一下。
1 . 缺失索引
缺失索引是最多见的引发髙CPU和 I/O利用的缘由之一,当没有合适的索引用于支
持查询时,通常只能经过大面积扫描来获取所需的信息。一方面,这种扫描会形成SQL
Server须要处理不少非必要的数据;另一方面,因为须要加载不少非必要的数据到内存, 所以会引发内存压力,致使计划缓存被移除,更严重的是引发SQL Server必须从新编译、 优化查询,编译和优化也是高CPU开销操做。下面来看个例子,执行以下语句。
USE AdventureWorks2008R2 GOnode
SET STATISTICS TIME ON;
SET STATISTICS IO ON;
SELECT per.FirstName , per.LastName , p.Name , p .ProductNumber , OrderDate , LineTotal • soh.TotalDue FROM Sales.SalesOrderHeader AS soh INNER JOIN Sales.SalesOrderDetail sod ON soh.SalesOrderlD = sod.SalesOrderlD INNER JOIN Production.Product AS p ON sod.ProductlD = p.ProductID INNER JOIN Sales.Customer AS c ON soh.CustomerlD = c.CustomerlD INNER JOIN Person.Person AS per ON c.PersonlD = per.BusinessEntitylD WHERE LineTotal > 25000
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
得 到 下 面 的 信 息 ,
SQL Server执行时间:
C P U 时间= 0 毫秒,占用时间= 0 毫秒。
SQL Server执行时间:
C P U 时间= 6 2 毫秒,占用时间= 9 7 毫秒。
SQL Server执行时间:
C P U 时间= 0 毫秒,占用时间= 0 毫秒。
能够看到,因为缺乏索引,CPU须要花费不少时间去获取没必要要的数据。咱们能够创
建一个索引,而后再看看结果。
CREATE NONCLUSTERED INDEX idx_SalesOrderDetail_LineTotal ON Sales.SalesOrderDetail (LineTotal)
索 引 后 结 果 如 下 ,
SQL Server执行时间:
CPU时间= 0 毫秒,占用时间= 0 毫秒。
SQL Server执行时间:
CPU时间= 0 毫秒,占用时间= 1 毫秒。
SQL Server执行时间:
C P U 时间= 0 毫秒,占用时间= 0 毫秒。
有了索引,占用时间几乎为0 , 可见效果很明显。在本人的工做经历中,缺失索引是最
常见的CPU消耗缘由。数据库
2. 统计信息过期
SQL Server优化器借助统计信息来预估查询状况,若是统计信息过期、不许 确 ,会导 致优化器产生不合适的执行计划,好比表中只有几万数据,而统计信息显示有几亿,这时
候优化器可能会选择hash链接,这将加大各方面的资源开销。对于这类问题,能够在经过 执行计划来查看,若是一个查询,你明知道它返回和处理的结果集都很小,而优化器却选
择了 hash链接,那么这时就能够检查一下图形化执行计划中是否有黄色叹号,或者用文本 化执行计划看看预估和实际行数的差别是否很大。若是是,可使用UPDATE STATISTICS
语句更新统计信息,同时检查为何统计信息过期。
3. 非 SARG查询
SARG是 Search Argument的缩写。简单来讲,若是一个谓词(特别是WHERE条件
中)能用到索引查找操做,就能够理解为符合SARG。可是若是在WHERE条件所用到的
列中使用了标量函数(YEAR、UPPER等),或者使用了 LIKE •%%’这类的查询,就称为非
SARG査询,会致使索引无效。这些非SARG的写法使得SQL Server只能进行表或者索引 扫描,结果相似于缺失索引,因此一样会引发CPU高利用。下面是典型的非SARG查询:
SELECT soh.SalesOrderlD , OrderDate , DueDate , ShipDate , Status , SubTotal ,
TaxAmt ,
Freight , TotalDue FROM Sales.SalesOrderheader AS soh INNER JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderlD = sod.SalesOrderlD WHERE CONVERT (date, sod.ModifiedDate) = *07/01/2005'
查看上面查询的执行计划能够看出,这里进行的是汇集索引扫描,如图4-3所示。但
原本是能够进行汇集索引査找的。
面对这类问题时,能够对语句进行改写。
SELECT soh.SalesOrderlD , OrderDate , DueDate , ShipDate , Status , SubTotal ,
TaxAmt ,
Freight , TotalDue FROM S a l e s . S a le s O r d e r h e a d e r AS so h INNER JOIN S a le s . S a le sO rd e rD e ta il AS sod ON so h .S alesO rd erlD = so d .S alesO rd erlD WHERE s o d . M odifiedD ate >= * 2 0 0 5 -0 7 -0 1 0 0 :0 0 :0 0 .0 0 0 * AND s o d . M odifiedD ate < * 2 0 0 5 -0 7 -0 2 0 0 :0 0 :0 0 .0 0 0 *缓存
另外,常见的非SARG查询还包括对where条件中的列使用一些如UPPER/LTRIM/ ISNULL之类的标量函数。对于这类状况,绝大部分状况下经过改写查询都能解决。函数
4 . 隐式转换
隐式转换(执行计划中会出现Implicit conversions)指一个查询的FROM/WHERE子句
中,用于关联或判断的列之间数据类型不相等,致使优化器须要根据数据类型的优先级高
低进行类型转换而后再优化、执行。因为SQL Server没法匹配不一样类型的数据,因此须要 先将它们转换成相同类型再进行匹配。这个步骤发生在查询执行过程当中。可是若是出现这
种状况,会致使查询转变成非SARG查询,从而出现相似上面介绍的问题。好比下面这个优化
查询:
SELECT p .FirstName , р . LastName , с. AccountNumber FROM Sales.Customer AS c INNER JOIN Person.Person AS p ON c.PersonlD = p.BusinessEntitylD
WHERE AccountNumber = N*AW00029594'执行上面的语句,并查看实际执行计划中
的图标属性,如图4-5所示。spa
、线程
在图4-5中,加框部分就是查询须要把varchar类型隐式转换成nvarchar类型。解决这 类问题最好的方法是在设计过程当中就先考虑数据类型,而且确保在where条件中变量、参 数、常量等都和数据列的类型一致。若是没法作到,能够考虑在传人where条件以前先进 行屋式数据类型转换。
5 . 参数嗅探
参数嗅探( Parameter sniffing)是 SQL Server建立针对存储过程、函数或者参数化查
询的执行计划时,根据传人的参数进行预估并生成(或重用)执行计划的一个功能。一般来
说,参数嗅探方式是比较好的功能,由于能够重用计划缓存。可是有些时候,针对一个查
询的第一次传参已经产生了一个执行计划,当后续传参时,因为存在对应参数的数据分布
等问题,致使原有的执行计划没法髙效响应请求,这时候就会出现参数嗅探问题。参数嗅
探仅出如今执行计划的编译或者重编译过程当中。设计
来看看下面这个例子:
—建立存储过程
CREATE PROCEDURE user_GetCustomerShipDates
(
@ShipDateStart DATETIME , @ShipDateEnd DATETIME
)
AS
SELECT CustomerlD , SalesOrderNumber FROM Sales.SalesOrderHeader WHERE ShipDate BETWEEN @ShipDateStart AND @ShipDateEnd
GO
--建立非汇集索引,演示完毕后请删除
CREATE NONCLUSTERED INDEX IDX_ShipDate_ASC ON Sales.SalesOrderHeader (ShipDate)
GO
— 清空缓存
DBCC FREEPROCCACHE
EXEC user_GetCustomerShipDates * 2005/07/08 *, *2008/01/01'
EXEC user_GetCustomerShipDates * 2005/07/10', 12005/07/20 *3d
从 图 4-6能够看到,虽然在ShipDate上有索引,但仍是进行的汇集索引扫描。这是因 为在第一个存储过程的参数中,查询条件的时间范围几乎包括了全表的全部时间,另外由
于非汇集索引没有覆盖查询(在第6 章介绍),所以使用了汇集索引扫描操做。而第二个存
储过程仍然会沿用上面的执行计划,可是实际上它只须要查询十天的数据,按理是不该该
存在扫描,可它仍是进行了汇集索引扫描。如今把上面的存储过程顺序调换一下,注意先
清空计划缓存。
DBCC FREEPROCCACHE -清空计划缓存,避免原有执行计划对本例形成影响
EXEC user_GetCustomerShipDates '2005/07/10', ,2005/07/20'
EXEC user_GetCustomerShipDates '2005/07/08', '2008/01/01'blog
从图4-7能够看到,两个查询都使用了索引查找。若是打开SET STATISTICS IO/TIME
这两个配置,而后对比上面两次查询的CPU时间,会发现前者第一个查询的CPU时间远
大于第二个,然后者的第二个时间远大于第一个。这是由于对于范围比较大的那个参数区
间,CPU须要处理更多的数据。
对于参数嗅探问题,可使用部分重编译、编译提示(OPTIMIZE FOR)等功能来避
免,更多的优化应该考虑数据和研究数据分布问题。
6 . 非参数化Ad-hoc查询
Ad-hoc称为即席查询,能够理解为没有使用存储过程、SP_ExeCUteSql或者其余方式强 制预约义SQL语句。这类查询会致使SQL Server每次都要检查是否有计划缓存可用于彻底 匹配这些语句。在这类语句中,即便只有参数不一样,都会致使CPU分别进行编译和优化。
而这将致使CPU的浪费(浪费在对原本能够重用的执行计划上),也会由于对Ad-hoc查询 分别存放执行计划(可能只会用一次)致使计划缓存空间的浪费。可使用下面的计数器来
进行监控,看看是否存在这样的浪费。
□ SQL Server: SQL Statistics: SQL Compilations/Sec □ SQL Server: SQL Statistics: Auto-Param Attempts/Sec □ SQL Server: SQL Statistics: Failed Auto-Param/Sec 若是是非参数化的A d-hoc,即不带参数的A d-hoc,好比select * from tb where id =xxx
这类查询引发了问题,在 SQL Server 2008中,可使用图4-8所 示 的 “高级”选项来对其 进行优化。
或者在数据库层面强制参数化。
ALTER DATABASE AdventureWorks SET PARAMETERIZATION FORCED
7 . 非必要的并行查询
并行操做会把一个査询分开到多个线程中执行,而后再合并到一块儿返回结果。当一个
査询的开销超过cost threshold for parallelism这个阈值时(默认为5 秒),就会检查是否有可
用 的 CPU用于支持并行操做,其中,并行度取决于max degree of parallelism的值。 可是 对于OLTP系统,并行操做每每是非必需的,过多的并行执行会加剧CPU的负担。能够用
下面语句来检查是否存在并行操做的执行计划。SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;WITH XMLNAMESPACES(DEFAULT •http://schemas.microsoft.com/SQL Server/2004/07/showplan*) SELECT query_plan AS CompleteQueryPlan , n.value(•(@StatementText)[1]•, *VARCHAR(4000)*) AS StatementText , n.value(1 (QStatementOptmLevel) [1]*, ’VARCHAR(25) 1) AS StatementOptimizationLevel , n.value(*(QStatementSubTreeCost)[1]、 *VARCHAR(128)*) AS StatementSubTreeCost , n.query(•.') AS ParallelSubTreeXML , ecp.usecounts f ecp.size_in_bytes FROM sys.dm_exec_cached_plans AS ecp CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS eqp CROSS APPLY query_plan.nodes (•/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS qn ( n ) WHERE n.query(*.*).exist(*//RelOp[@PhysicalOp=M Parallelism” ]') = 1对于存在并行操做的査询,不建议立刻下降并行度,应该优化查询,使其尽量保持 在并行开销的阈值之内。CPU髙利用的状况可能会有不少,这里只给出几个常见的类型及 对应的处理方法,在后续的章节中会陆续进行进一步的描述。