《Troubleshooting SQL Server》讀書筆記-CPU使用率過高(下)


第三章 High CPU Utilization.

CPU使用率過高的常見原因

  查詢優化器會盡量從CPU,IO和內存資源成本最小的角度,找到最高效的數據訪問方式。如果沒有正確的索引,或者寫的語句本身就會忽略索引,

  又或者不准確的統計信息等情況下,查詢計划可能不是最優的。

  有些查詢計划可能對只對某種條件下的查詢是高效,而不是所有條件下都是。

缺失索引

    索引的缺失,會導致查詢處理的行數大大超出必要的行數,從而加重CPU和IO的負載。簡單的例子:

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.SalesOrderID = sod.SalesOrderID INNER  JOIN Production.Product  AS p  ON sod.ProductID  = p.ProductID INNER  JOIN Sales.Customer AS c  ON soh.CustomerID = c.CustomerID INNER  JOIN Person.Person AS per ON c.PersonID = per.BusinessEntityID WHERE    LineTotal > 25000 SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 452 ms,  elapsed time = 458 ms.

上面的查詢使用AdventureWorks2008數據庫,字段LineTotal上沒有索引,會導致SalesOrderDetail全表掃描。然后創建如下索引后,改善很明顯:

CREATENONCLUSTEREDINDEX idx_SalesOrde ON Sales.SalesOrderDetail (LineTotal) SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server Execution Times: CPU time = 0 ms,  elapsed time = 8 ms.

 

過期的統計信息

     查詢優化器使用統計信息計算各種查詢操作的基數(開銷)。查詢操作的成本(cost)又決定了查詢計划的成本。過期的統計信息會導致生成非最優的查詢計划,

如預估成本很低,但實際成本很高的計划。

最常見就是預估行數很少,並選擇了那些適合少量數據的操作(如嵌套循環,LookUp),但當實際執行時要處理的行數卻很多,查詢效率就變得很低。

可以通過SSMS或者set statistics profile on為索引查找和掃描操作,返回實際行數與預估行數做比較。如果兩者差異較大,就很有可能統計信息過期了。

過期時,可以使用update statistics tableName更新表上所有的統計信息,update statistics tableName statisticsName更新指定統計信息。

為了防止統計信息過期的問題,有如下三種方法:

   a. 開啟數據庫的Auto_Update_Statistics選項或者用定時作業更新全庫的統計信息。

   b. 如果某些索引的自動更新統計信息被禁用,則需要指定STATISTICS_NORECOMPUTE=OFF重建索引開啟。

   c. 對於某些經常因為統計信息過期而導致性能問題的統計信息,可以創建定時作業頻繁地更新它們。

 

非SAGR謂詞

SAGR=Search Agrument.簡單說就是能夠使用索引查找的謂詞。列應該直接與表達式進行比較則符合SAGR,如WHERE  SomeFunction(Column) = @Value就符合,    

WHERE Column = SomeOtherFunction(@Value) 則符合。注意LIKE和BETWEEN也是SAGR謂詞。

非SAGR會導致表或者索引掃描,它的影響跟缺失索引類似。使得CPU處理大量非必需的數據行。下面查詢會導致索引掃描:

SELECT soh .SalesOrderID , OrderDate , DueDate , ShipDate , Status , SubTotal , TaxAmt , Freight , TotalDue FROM    Sales.SalesOrderheader  AS soh INNER  JOIN Sales.SalesOrderDetail  AS sod ON soh.SalesOrderID = sod.SalesOrderID WHERE     CONVERT(VARCHAR(10), sod.ModifiedDate , 101) = '01/01/2010'

改寫成如下則會使用索引查找:

SELECT soh .SalesOrderID , OrderDate , DueDate , ShipDate , Status , SubTotal , TaxAmt , Freight , TotalDue FROM    Sales.SalesOrderheader  AS soh INNER  JOIN Sales.SalesOrderDetail  AS sod  ON soh.SalesOrderID = sod.SalesOrderID WHERE    sod.ModifiedDate >= '2010/01/01'  
         AND  sod.ModifiedDate < '2010/01/02'

UPPER,LOWER,LTRIM,RTRIM,ISNULL這些經常會被濫用,甚至用於WHERE和JOIN條件中。

在不區分大小寫排序規則中,大小寫被視為相等的,像UPPER,LOWER這種拖累性能的函數就不必要用了。

SQL中字符串比較會忽略末尾空格,所以RTRIM也沒必要用。

下面兩個過濾條件,前者,字段NULL值轉換成0從而被排除;后者中,其實NULL值與任何值比較操作都不會返回TURE,而被排除。

NULL值只在IS NULL或者IS NOT NULL檢查時才可能返回TRUE。所以是等效的,但后者才能使用索引查找。

WHERE  ISNULL(SomeCol,0) > 0
WHERE  SomeCol > 0

 

隱式轉換

     隱式轉換發生在比較兩個不同數據類型時。SQL不能對不同類型數據進行比較,所以查詢優化器會在比較操作前把低優先級的數據類型轉換成高優先級的數據類型再比較。

這跟非SARG謂詞一樣,將不能使用Index Seek,從而處理很多不必要的數據行,增加CPU開銷。最常見例子是使用NVARCHAR類型的參數與VARCHAR類型的列進行比較。如:

 
SELECTp .FirstName , p.LastName , c.AccountNumber FROM Sales.Customer ASc 
INNER JOINPerson.Person AS p ON c.PersonID = p.BusinessEntityID WHERE AccountNumber = N'AW00029594'
上面的查詢導致一個非聚集索引掃描,在Filter操作中會看有一個COVERT_IMPLICIT。

為了避免隱式轉換:

    1. JOIN的列,數據類型盡量相同

    2. 與列比較時,任何參數,變量和常量的類型要和列的類型相同

    3. 當參數,變量或常量的類型與要比較的列不同時,斟酌地使用類型轉換函數,使其與列類型相同

    4. 有些數據訪問組件和開發框架會把字符串類型默認地設置為NVARCHAR

 

參數探測(Parameter Sniffing)

      參數探測是SQL Server為存儲過程,函數和參數化查詢創建查詢計划時用到的處理方式。當首次編譯查詢計划時,SQL Server會檢測或者探測輸入參數的值並結合統計信

息,預估受影響的行數,

並以之估算查詢計划成本。當根據傳入的參數值創建查詢計划,得到的受影響行數不是典型的情況時,就產生問題了。參數探測只出現在編譯和重編譯時,之后的存儲過程,函數和

參數化查詢,

會重用此查詢計划。最初編譯時只有輸入參數的值會被探測到,本地變量是沒有值的。如果批處理中的語句被重編譯,則參數和變量將會被賦值並探測到。示例如下:

 
         
CREATEPROCEDUREuser_GetCustomerShipDates ( @ShipDateStart DATETIME , @ShipDateEnd DATETIME ) AS
SELECT CustomerID , SalesOrderNumber FROM Sales.SalesOrderHeader WHERE ShipDate BETWEEN @ShipDateStart AND @ShipDateEnd 
GO
Sales.SalesOrderHeader表的ShipDate字段范圍是2004-08-07~2011-08-07,並創建非聚集索引: 
CREATENONCLUSTEREDINDEX IDX_ShipDate_ASC ON Sales.SalesOrderHeader (ShipDate ) GO 首先我們執行兩次SP,並用DBCC FREEPROCCACHE在運行前清空計划緩存: DBCC FREEPROCCACHE EXEC user_GetCustomerShipDates '2001/07/08', '2004/01/01' 
EXEC user_GetCustomerShipDates '2001/07/10', '2001/07/20'
查詢計划如圖: 
image
查詢並沒有使用ShipDate列非聚集索引,因為它不是一個覆蓋索引,並且被執行時,查詢優化器根據參數值結合統計信息預估的行數很多,使用IndexSeek和LookUp的組合成本
 
太高。再觀察STATISTICS IO&TIME:
==FIRST EXECUTION (LARGE DATE RANGE)=== (Table 'SalesOrderHeader'. Scan count 1, logical reads 686, physical reads 0. SQL Server Execution Times: CPU time = 16 ms,  elapsed time = 197 ms. SQL Server Execution Times: CPU time = 16 ms,  elapsed time = 197 ms. ==SECOND EXECUTION (SMALL DATE RANGE)=== 
 
Table 'SalesOrderHeader'. Scan count 1, logical reads 686, physical reads 0. SQL Server Execution Times: CPU time = 15 ms,  elapsed time = 5 ms. SQL Server Execution Times: CPU time = 15 ms,  elapsed time = 5 ms.
兩者的純CPU時間和IO是基本一樣,因為前者需要處理的數據量多很多,所以CPU消耗時間長一些。接下來,調換兩個執行SP的順序,再執行:
DBCC FREEPROCCACHE EXEC user_GetCustomerShipDates '2001/07/10', '2001/07/20' 
EXEC user_GetCustomerShipDates '2001/07/08', '2004/01/01'
image
==FIRST EXECUTION (SMALL DATE RANGE)===  
 
Table 'SalesOrderHeader'. Scan count 1, logica ahead reads 0, lob logical reads 0, lob physic SQL Server Execution Times: CPU time = 0 ms,  elapsed time = 0 ms. ==SECOND EXECUTION (LARGE DATE RANGE)=== 
 
Table 'SalesOrderHeader'. Scan count 1, logica SQL Server Execution Times: CPU time = 47 ms,  elapsed time = 182 ms.
這次兩者性能差距就很明顯了。參數探測導致優化器采用了適合少量數據的KeyLookUp操作,而第二次查詢重用了此查詢計划,但是實際它需要處理大量數據,
 
這時KeyLookUp就導致了明顯的性能問題,需要額外的IO和CPU資源。 
 
根據具體的環境和SQL Server版本,有多種處理參數探測的方法:
1. 跟蹤標志4136
      此跟蹤標志使得SQLServer實例不再使用參數探測,而是使用列平均重復個數(=總行數/列的非重復值個數)來估算受影響行數。 這樣的估算值是不精確的。
 
啟用此標志將會使得那些正確的參數探測的情況,變得不准確,帶來負面影響。所以應該做為最后的手段。
 
適用於SQL Server 2008 SP1 CU7,SQL Server 2008 R2 CU2,SQL Server 2005 in SP3 CU9。 
 
2. 使用OPTIMIZE FOR查詢提示 SQLServer 2005及后續版本中,可以為查詢優化器編譯查詢計划時指定參數的值。如:
CREATE PROCEDURE user_GetCustomerShipDates ( @ShipDateStart DATETIME , @ShipDateEnd DATETIME ) AS   
    SELECT CustomerID , SalesOrderNumber FROM Sales.SalesOrderHeader WHERE    ShipDate  BETWEEN @ShipDateStart AND  @ShipDateEnd 
    OPTION   ( OPTIMIZE  FOR ( @ShipDateStart = '2001/07/08' , @ShipDateEnd = '2004/01/01' ) ) GO
在2008中還能OPTIMIZE FOR UNKNOWN使得優化器不用參數探測,這個跟T-4136一樣,只不過是語句級。
        
3. 重編譯選項   

         在創建存儲過程可以指定WITH RECOMPILE重編譯選項。指定后SP每次執行時會基於當前參數值重新編譯,同時也不緩存執行計划。但是這樣會增加執行處理時間。

CREATE PROCEDURE user_GetCustomerShipDates ( @ShipDateStart DATETIME , @ShipDateEnd DATETIME ) WITH RECOMPILE AS   
                   SELECT CustomerID ,SalesOrderNumber FROM Sales.SalesOrderHeader WHERE    ShipDate  BETWEEN @ShipDateStart AND  @ShipDateEnd

   當SP中的多個語句,只是某個語句會產生參數探測問題,則可以對這個語句使用OPTION(RECOMPILE)查詢提示。這樣每次執行時只會對這個語句重編譯,

    而不像WITH RECOMPILE對整個SP重編譯。如果可能盡量使用查詢提示,減少重編譯的影響范圍和開銷。

CREATE PROCEDURE user_GetCustomerShipDates ( @ShipDateStart DATETIME , @ShipDateEnd DATETIME ) AS   
              SELECT CustomerID , SalesOrderNumber FROM Sales.SalesOrderHeader WHERE    ShipDate  BETWEEN @ShipDateStart AND  @ShipDateEnd 
              OPTION ( RECOMPILE )
 
即席非參數化查詢
    即席查詢不能重用執行計划,每次執行時都會被編譯,消耗大量資源(特別是CPU)。像下面的查詢,每次因為WHERE條件中參數值不同而產生不同的執行計划。
    雖然SQL Server有簡單參數化(Simple Parameterization)的技術,但是此語句相對”太復雜”了。
SELECT soh .SalesOrderNumber , sod.ProductID FROM    Sales.SalesOrderHeader  AS soh INNER  JOIN Sales.SalesOrderDetail  AS sod ON soh.SalesOrderID = sod.SalesOrderID WHERE    soh.SalesOrderNumber  = 'SO43662'
    非參數化查詢主要有兩方面的影響:
        1. 即席查詢產生的一次性的查詢計划會填滿計划緩存。由此帶來的內存壓力,會讓那些本可以重用的計划迫於內存壓力而被清除掉等等。
        2. 編譯這些一次性的查詢計划浪費了大量的CPU資源。
    可以用下面的計數器來判斷即席非參數化查詢的影響程度: 

SQLServer: SQL Statistics: SQL Compilations/Sec
SQLServer: SQL Statistics: Auto-Param Attempts/Sec
SQLServer: SQL Statistics: Failed Auto-Param/Sec

解決的方法有:

1. 修改應用程序代碼,使發送到SQL Server語句盡量被參數化。

2. 在SQL Server 2005及以上版本中,能在數據庫級別設定強制參數化。但可能會帶來類型參數探測一樣的問題。

ALTER  DATABASE  AdventureWorks SET  PARAMETERIZATION FORCED
 3. SQL Server 2008及以上版本中,啟用實例級別的optimize for  ad hoc  workloads。
        啟用后當即席查詢第一次執行時只保存查詢計划的一個“存根”,第二次執行時則緩存執行計划。“存根”使用很少的內存,
        這樣就減少了那些本可以重用的執行計划,因內存壓力而被清除掉的機率。
EXEC sp_configure  'show advanced options',1 
              RECONFIGURE 
              EXEC sp_configure  'optimize for ad hoc workloads',1 
              RECONFIGURE
不恰當的並行
   並行查詢是把一個查詢的工作分解成多個線程執行,每一個線程使用單獨的計划程序。查詢並行發生在操作符(Operator)級別。
查詢優化器在編譯執行計划時總是會讓其盡可能的快。如果執行計划的預估成本連續超過cost threshold of prillelism,同時SQL Server可用
CPU個數多於1個,並且max degree of prallelism為0或大於1,則產生的執行計划將會包括並行。
並行查詢通過水平分割輸入數據,然后分布到多個邏輯CPU上同時執行,從而減少執行時間。對於數據倉庫和報表系統會有好處,對於OLTP系統
則並行會占用過多的CPU資源,而其它的請求不得不等待CPU資源。
SQL Server有兩個控制並行執行的sp_configure選項:
   cost threshold of prillelism:控制優化器為查詢使用並行執行的閥值
   max degree of prallelism:避免單個查詢用完所有可用的處理器內核
 cost threshold of prallelism的默認值為5秒。在較大的數據庫上,默認閥值可能太低了,會導致並行執行的資源爭用。
 可以通過下面的查詢獲取存於計划緩存中的並行執行計划,做為調整cost threshold of prillelism的重要參考依據:
SET  TRANSACTION ISOLATION LEVEL  READ UNCOMMITTED ; WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan') SELECT query_plan  AS CompleteQueryPlan , n.value ('(@StatementText)[1]', 'VARCHAR(4000)' ) AS StatementText , n.value ('(@StatementOptmLevel)[1]', 'VARCHAR(25)') AS StatementOptimizationLevel , n.value ('(@StatementSubTreeCost)[1]', 'VARCHAR(128)') AS StatementSubTreeCost , n.query ('.' ) AS ParallelSubTreeXML , ecp.usecounts , 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="Parallelism"]' ) = 1
 
並行中使用到處理器數是取自后面三者中的最小值:max degree of prallelis的值,可用的處理器數和MAXDOP查詢提示(會覆蓋max degree of prallelis指定的值)。
合適的max degree of prallelis值取決於工作負載類型和硬件資源支撐並行開銷的能力。很多磚家推薦將其設定為1,前提是你的系統是真正的單獨的OLTP系統,
只有大量的並發的小事務。對於NUMA和SMP系統的設定是不同的。SQL Server 2008及以上版本利用resource governor能將max degree of prallelis綁定到特定的查詢組。
 
當系統出現並行性能問題時,通常會CXPACKET等待會比較明顯,其實它很冤,它只是個表面現象。根本原因還是要查看產生CXPACKET等待的子線程的等待類型。
如果伴隨着如IO_COMPLETION,ASYNC_IO_COMPLETION,PAGEIOLATCH_*的等待,則要提高IO性能;
如果伴隨着LATCH_*  and SOS_SCHEDULER_YIELD,則表示並行本身導致了性能問題,如果此時還有ACCESS_METHODS_DATASET_PARENT等待,則執行的並行度是根本原因。
出現這些問題時首先應該優化並行執行的語句,其次才是結合max degree of prallelis和cost threshold of prillelism兩者進行限制,再次是提升硬件。
在硬件暫時無法提升時,只能對並行做一定限制,做折中和權衡考量。
 
在SQL Server 2005,有個關於TokenAndPermUserStore的CPU問題。當數據庫用戶很多,adhoc和動態查詢很多且非AWE內存空間很多時,

可能會出現CMEMTHREAD大量等待和TokenAndPermUserStore使用過量並持續增長。微軟已經推出的解決方案http://support.microsoft.com/kb/927396
長期的解決方案是修改程序架構,盡量減少可能導致此問題的adhoc和動態查詢的使用。
短期解決方案是:
   1. 給應用程序賬戶提升為sysadmin,從而規避權限檢查而極大的減少TokenAndPermUserStore的緩存使用量。這是有安全風險的
   2. 使用DBCC FREESYSTEMCACHE ('TokenAndPermUserStore'),定期清理這一部分緩存。
   3. SQL Server 2005 SP2及以上版本,可以使用Trace Flag 4618&4610.4610限制緩存條目為1024,兩者都啟用則限制緩存條目為8192.
   4. SQL Server 2005 SP3及以上版本,使用Trace Flag 4621,可以設定緩存配額。 http://support.microsoft.com/kb/959823。
   5. SQL Server 2008有access check cache bucket count & access check cache quota兩個sp_configure項,
      用於設置TokenAndPermUserStore的hash bucket數量和緩存條目數。
 
關於超線程和BIOS節能控制選項
   很多磚家推薦SQL Server服務器不宜開啟超線程,根據作者的研究,這種推薦做法在過去是對的,現在不一定是對的。
在過去,超線出來的CPU會一起共享板載緩存,而這個緩存是KB量級的。而且windows 2000本身不支持超線程,“超”出來的它會認為是物理CPU。“CPU”一多就會造成緩存命中率低下,進而影響性能。

而很多DBA管理的OLTP&DSS的混合環境,那么超線程對於那些DSS型查詢的提升,在混合環境中很不明顯,甚至有時會導致需要迅速響應OLTP弄查詢等待。
現在,硬件和軟件進步了,現在板載緩存量級都是MB級的了,CPU緩存命中率不再是問題。而windows 2003也支持超線程了。而且現在OLTP,OLAP,DW等系統一般會隔離使用。
當然是否開啟還是需要經過測試才能最終決定。
 
綠色節能現在也是硬件和服務器一種標准了,自動降低系統中某些暫時未用到的硬件能耗和CPU頻率。BioS中可以設定為硬件自己控制或者OS控制。
windows 2008&r2默認設定電源計划為“平衡”,以允許其切換到節能模式。有時在windows 2008 or R2的新服務器升級系統一段時間后,性能下降明顯,這就是電源管理造成的CPU降頻導致的。
windows的性能計數器 % Processor Usage 是已使用CPU頻率除以可用CPU頻率得到的。如果CPU被降頻了,那么這個計算器會較高,會讓人誤會成工作負載很高。
可以使用CPU-Z來檢測CPU的狀態。
建議將windows的電源計划調定為“高性能”,並且檢查BIOS的電源控制選項,設定為OS Control.
 
總結:
   1. SQL SERVER CPU調整的地方很少,很多時候還是因為語句性能低下導致CPU使用過高。
   2. 解決參數嗅探問題,還有拼動態語句,使用plan_guide等方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM