性能調優是DBA的重要工作之一。很多人會帶着各種性能上的問題來問我們。我們需要通過SQL Server知識來處理這些問題。經常被問到的一個問題是:早上這個存儲過程運行時間還是可以的,但到了晚上就很慢很慢。對此,我們可以笑着回答:這個存儲過程運行多次后,已經累趴了,所以很慢。
存儲過程或語句運行時間取決於服務器的工作量。如果在晚上,服務器負擔很重的話,你的存儲過程可能需要更多的時間來運行,因為它在等待CPU周期(CPU cycle)和IO完成(IO completion)。為了獲得一致的響應時間,我們需要減少執行完成的資源需求,那就是所謂的性能調優。
IO和CPU是完成執行的主要資源使用對象。更少的資源使用,更穩定的性能表現。這篇文章我們來理解下性能調優中DBCC STATISTCS IO所扮演的角色。
默認情況下SET STATISTCS IO是停用的,我們可以通過下列語句在當前會話級別打開。
1 SET STATISTICS IO ON
這個語句可以幫助我們獲得在語句執行時,所發生IO數(頁讀/寫)。我們來看一個例子的輸出。
1 USE StatisticsDB 2 GO 3 SELECT * INTO SalesOrderDetail FROM AdventureWorks2008R2.Sales.SalesOrderDetail 4 GO 5 SET STATISTICS IO ON 6 DBCC dropcleanbuffers 7 DBCC freeproccache 8 GO 9 SELECT * FROM SalesOrderDetail 10 GO 11 SELECT * FROM SalesOrderDetail
Set Statistics IO的輸出信息可以在消息TAB頁里找到。同樣的語句我們執行了2次,第一次是在清空緩存后執行,第2次沒有。
我們來看下輸出信息:
掃描計數(Scan count):
根據微軟在線幫助,掃描計數是在任何方向都達到葉級別后啟動的查詢/掃描數,目的在於檢索用於構造輸出的最終數據集的所有值。
- 如果使用的索引是主鍵的唯一索引或聚集索引並且您僅查找一個值,則掃描計數為 0。 例如 WHERE Primary_Key_Column = <value>。
-
當您使用對非主鍵列定義的非唯一的聚集索引搜索一個值時,掃描計數為 1。 這是為了針對您正在搜索的鍵值檢查重復值。 例如 WHERE Clustered_Index_Key_Column = <value>。
-
當 N 為通過使用索引鍵定位鍵值后,在葉級別的左側或右側啟動的不同查找/掃描數時,則掃描計數為 N。
這個數字告訴我們優化器所選擇的計划,對這個對象的重復讀取次數。很多人誤以為這個是對整張表的讀取次數,這是完全錯誤的。
我們通過一個例子來理解掃描計數。
1 CREATE TABLE ScanCount (Id INT IDENTITY(1,1),Value CHAR(1)) 2 INSERT INTO ScanCount (Value ) VALUES ('A') ,('B'),('C'),('D'), ('E') , ('F') 3 CREATE UNIQUE CLUSTERED INDEX ix_ScanCount ON ScanCount(Id) 4 5 SET STATISTICS IO ON 6 --Unique clustered Index used to search single value 7 SELECT * FROM ScanCount WHERE Id =1 8 --Unique clustered Index used to search multiple value 9 SELECT * FROM ScanCount WHERE Id IN(1,2,3,4,5,6) 10 --Unique clustered Index used to search multiple value 11 SELECT * FROM ScanCount WHERE Id BETWEEN 1 AND 6
我們來看下上面3個查詢語句的輸出。
在第1個SELECT語句的輸出里,掃描計數為0。這和MSDN里在線幫助“如果使用的索引是主鍵的唯一索引或聚集索引並且您僅查找一個值,則掃描計數為 0。”描述一致。因為它是唯一索引(聚集/非聚集索引),不需要在葉子層,進行進一步的向左或向右掃描,因為這里只有一個值來匹配。那也是在唯一索引上查找單一值,掃描計數為0的原因。掃描計數是1的話,會在非唯一索引(聚集或非聚集索引)上發生。
對於第2個SELECT語句,掃描計數是6.這是因為我們在找多個不同值。MSDN在線幫助對此有詳細說明: “如果使用的索引是主鍵的唯一索引或非聚集索引,你在查找N個值,則掃描計數為N。”。
我們來看看執行計划里的SEEK謂語,將更清晰:
即使只有一個where條件,還是會分裂成多個謂語。對於每個SEEK謂語,它會生成1個掃描數。
對於最后一個SELECT語句,掃描計數為1,因為MSDN在線幫助說了: “當 N 為通過使用索引鍵定位鍵值后,在葉級別的左側或右側啟動的不同查找/掃描數時,則掃描計數為 N。” 在葉子節點聚集索引結構用來找到1值后,葉子層的向左掃描開始,直到找到值6。我們看下執行計划里的SEEK 謂語,將更清晰:
邏輯讀取(logical Read):
從數據緩存讀取的頁數。數字越小,性能越好。在性能調優中這個數字非常重要。因為它不會隨着執行又執行而改變,除非數據或查詢語句有變動。在進行性能調優時,這個可以作為性能提升的重要參考。
物理讀取(physical reads):
從磁盤讀取的頁數。這個會隨着執行又執行而改變。大多數情況下,連續第2次的執行時,它的物理讀取值為0(可以參考上面連續查詢的物理讀取數變化)。
如果連續執行后,物理讀取次數下降了,我們可以假定是服務器上內存使用配置的錯誤,或者服務器工作量飽和,有內存壓力。你需要在服務器級別思考問題的原因。在查詢調優時,這個數字不太重要,因為它一直在變,對於下降這個值,你不能對它做出太多控制。
預讀 (read-ahead reads):
為進行查詢而放入緩存的頁數。這個值告訴我們物理頁讀取數,即SQL Server執行的,作為預讀機制的一部分。在查詢執行請求那些可能用到頁之前,SQL Server把物理數據頁讀入緩存,用於完成接下來查詢的頁需要。
可以看到,物理讀取是2次,預讀是946次。這就是說,查詢執行請求了2個頁,並預讀了946個頁到數據緩存,SQL Server估計下次查詢可能要用到這些頁。和物理讀取一樣,這個值對在查詢調優里並不重要。
lob 邏輯讀取(lob logical reads):
從數據緩存讀取的 text、ntext、image 或大值類型 (varchar(max)、nvarchar(max)、varbinary(max)) 頁的數目。這個和邏輯讀一樣重要,我們要非常重視。
lob 物理讀取(lob physical reads):
從磁盤讀取的 text、ntext、image 或大值類型頁的數目。
lob 預讀(lob read-ahead reads):
為進行查詢而放入緩存的 text、ntext、image 或大值類型頁的數目。
總結下,邏輯讀取和LOB邏輯讀取是2個重要數值,在性能調優時,我們要重點圍觀。如果把這2個值調低,不在本文的討論范圍。通常創建合適的索引或重寫查詢可以幫助我們徹底降低這2個值。