當SQL Server的性能變差時,最可能發生的是以下兩件事:
- 首先,某些查詢產生了系統資源上很大的壓力。這些查詢影響整個系統的性能,因為服務器無法足夠快速地服務其他SQL查詢。
- 另外,開銷較大的查詢阻塞了其他請求相同數據庫資源的查詢,進一步降低了這些查詢的性能。優化開銷較大的查詢不僅改進它們本身的性能,而且減少數據庫阻塞和SQL Server資源壓力從而提高了其他查詢的性能。
識別開銷較大的查詢
SQL Server的目標是在最短時間內將結果集返回給用戶。為此,SQL Server查詢優化器生成一個成本效益高的查詢執行計划。查詢優化器計算許多因素的權重,包括執行查詢所需要的CPU、內存以及磁盤I/O的使用情況-這些均來自於由索引維護或過程中生成的統計。通常開銷最低的計划有最少的I/O,因為I/O操作代價昂貴。
邏輯讀提供指出了查詢產生的內存壓力。它還提供了磁盤壓力指標,因為內存頁面必須在操作查詢中被備份,在第一次數據訪問期間寫入,並且在內存瓶頸時被移到磁盤上。查詢的邏輯讀數量越大,磁盤壓力的可能性就越大。過多的邏輯頁面也增加了CPU用於管理這些頁面的負載。
導致大量邏輯讀的查詢通常在相應的大數據集上得到鎖。即使讀也需要在所有數據上的共享鎖。這些查詢阻塞了其他請求修改這些數據的查詢,但是不阻塞讀取數據的查詢。因為這些查詢固有的開銷並且需要長時間執行,他們持續地阻塞其他查詢。被阻塞的查詢進一步阻塞查詢,引入了數據中的阻塞鏈。
識別開銷較大的查詢並優化它們有如下意義:
- 增進開銷較大的查詢本身的性能;
- 降低系統資源上的總體壓力;
- 較少數據庫阻塞;
其中開銷較大的查詢可以被分為如下兩類:
- 單詞執行:查詢的一次單獨執行開銷較大;
- 多次執行:查詢本身開銷並不大,但是該查詢的重復執行導致系統資源上的壓力;
1、單次執行開銷較大的查詢
可以分析SQL Profiler跟蹤輸出文件來識別開銷較大的查詢。比如,如果對識別執行大量的邏輯讀的查詢感興趣,應該在跟蹤輸出的Reads數據列上排序。
- 捕捉表示典型工作負載的Profiler跟蹤;
- 將跟蹤輸出保存到一個跟蹤文件;
- 打開跟蹤文件進行分析;
- 通過事件選擇選項卡,單擊組織列按鈕,在Reads列上分組跟蹤輸出。
跟蹤輸出如下:
在某些情況下,可能從系統監視器輸出中識別CPU上的大壓力。CPU上的壓力可能是因為大量CPU密集型操作,如存儲過程重編譯、總計函數、數據排序、哈希連接等。在這種情況下,應該在CPU列上排序Profiler跟蹤輸出以識別使用大量處理器周期的查詢。
2、多次執行開銷較大的查詢
有時候一個查詢可能本身開銷並不大,但是同一查詢多次執行的累積效應可能造成系統資源的壓力。在Reads列上排序對識別這種類型的查詢沒有幫助。如果希望知道查詢的多次執行進行的總讀取數,不幸的是Profiler在這里不能直接提供幫助,但是仍然可以用以下方法得到這一信息。
- 在Profiler中跟蹤輸出的以下列上分組:EventClass、TextData和Reads。對於相同EventClass和TextData的分組,手工計算所有對應的Reads的總和。
- 在Profiler中選擇文件=》另存為=》跟蹤表將輸出到一個跟蹤表。也可以使用內建函數fn_trace_gettable和Profiler的跟蹤文件輸出導入到一個跟蹤表。
- 訪問sys.dm_exec_query_stats DMV從生產服務器上檢索信息。這假設打算處理一個即時的問題並且不關注歷史問題。
在將跟蹤輸入保存到文件以后,先將跟蹤數據導入到一張表:
SELECT * INTO TraceTable FROM ::fn_trace_gettable('D:\123.trc',default)
然后執行以下語句:
SELECT COUNT(*) AS TotalExecutions,EventClass, CAST(TextData AS NVARCHAR(MAX)) TextData, SUM(Duration) AS Duration_Total, SUM(CPU) AS CPU_Total, SUM(Reads) AS Reads_Total, SUM(Writes) AS Writes_Total FROM TraceTable GROUP BY EventClass,CAST(TextData AS NVARCHAR(MAX)) ORDER BY Reads_Total DESC
腳本中的TotalExecutions列指出了查詢被執行的次數,Reads_Total列指出了該查詢多次執行所進行的讀操作的總數。注意NTEXT不支持GROUP BY,因此要轉換一下類型。
這個方法識別出來的開銷較大的查詢比Profiler識別出的單次執行的開銷較大查詢更好地指出了負載。例如,一個需要50個讀操作的查詢可能執行1000次。這個查詢本身被認為足夠經濟了,但是執行的讀操作總是是5萬,這不能被認為是經濟的。優化這個查詢降低讀操作數,即使每次執行減少10次,讀操作數也將降低1萬次。這比優化一個5千次讀操作的查詢更有利。
從sys.dm_exec_query_stats視圖中得到相同的信息只需要一個查詢:
SELECT ss.sum_execution_count ,t.TEXT ,ss.sum_total_elapsed_time ,ss.sum_total_worker_time ,ss.sum_total_logical_reads ,ss.sum_total_logical_writes FROM (SELECT s.plan_handle ,SUM(s.execution_count) sum_execution_count ,SUM(s.total_elapsed_time) sum_total_elapsed_time ,SUM(s.total_worker_time) sum_total_worker_time ,SUM(s.total_logical_reads) sum_total_logical_reads ,SUM(s.total_logical_writes) sum_total_logical_writes FROM sys.dm_exec_query_stats s GROUP BY s.plan_handle )AS ss CROSS APPLY sys.dm_exec_sql_text(ss.plan_handle) t ORDER BY sum_total_logical_reads DESC
這比所有收集跟蹤數據所需要的工作要容易得多,那么為什么還要使用跟蹤數據?使用跟蹤的主要原因是精確性。sys.dm_exec_query_stats視圖是給定計划已經存在於內存中時的流動總計,時間點並不精確。另一方面,跟蹤是運行的任何時間段的歷史記錄。甚至可以在數據庫中加入跟蹤,並且擁有一系列可以比依靠給定的瞬間更精確地生成總計的數據。但是對定位性能問題的理解關注是查詢運行緩慢的時點,這是sys.dm_exec_query_stats不可替代的場合。
3、識別運行緩慢的查詢
如果運行緩慢的查詢的響應時間變得不可接受,那么應該分析性能下降的原因。但是不是所有運行緩慢的查詢都是由於資源問題造成的,其他需要關心的因素如阻塞也可能導致緩慢的查詢。
為了發現運行緩慢的查詢,在Duration列上分組跟蹤輸出。
跟蹤輸出如下:
對於運行緩慢的系統,應該注意優化過程前后運行緩慢的持續查詢時間。應用優化技術之后,應該計算在系統上的總體性能。優化步驟可能負面地影響其他查詢,使其變慢。