做爲一個DBA,排除SQL Server問題是咱們的職責之一,每月都有不少人給咱們帶來各類不能解釋卻要解決的性能問題。html
我就屢次聽到,之前的SQL Server的性能問題都還好且在正常範圍內,但如今一切已經改變,SQL Server開始糟糕, 瘋狂的事情不能解釋。在這個狀況下我介入,分析下整個SQL Server的安裝,最後用一些神奇的調查方法找出性能問題的根源。sql
但不少時候問題的根源是同樣的:所謂的計劃迴歸(Plan Regression),即特定查詢的執行計劃已經改變。昨天SQL Server已經緩存了在計劃緩存裏緩存了一個好的執行計劃,今天就生成、緩存最後重用了一個糟糕的執行計劃——不斷重複。數據庫
進入SQL Server 2016後,我就變得有點多餘了,覺得微軟引進了查詢存儲(Query Store)。這是這個版本最熱門的功能!查詢存儲幫助你很容易找出你的性能問題是否是計劃迴歸形成的。若是你找到了計劃迴歸,這很容易強制一個特定計劃不使用計劃嚮導。聽起來頗有意思?讓咱們經過一個特定的場景,向你展現下在SQL Server 2016裏,如何使用查詢存儲來找出並最終修正計劃迴歸。緩存
在SQL Server 2016裏,在你使用查詢存儲功能前,你要對這個數據庫啓用它。這是經過ALTER DATABASE語句實現,如你所見的下列代碼:性能
1 CREATE DATABASE QueryStoreDemo 2 GO 3 4 USE QueryStoreDemo 5 GO 6 7 -- Enable the Query Store for our database 8 ALTER DATABASE QueryStoreDemo 9 SET QUERY_STORE = ON 10 GO 11 12 -- Configure the Query Store 13 ALTER DATABASE QueryStoreDemo SET QUERY_STORE 14 ( 15 OPERATION_MODE = READ_WRITE, 16 CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 367), 17 DATA_FLUSH_INTERVAL_SECONDS = 900, 18 INTERVAL_LENGTH_MINUTES = 1, 19 MAX_STORAGE_SIZE_MB = 100, 20 QUERY_CAPTURE_MODE = ALL, 21 SIZE_BASED_CLEANUP_MODE = OFF 22 ) 23 GO
在線幫助爲你提供了各個選項的詳細信息。接下來我建立一個簡單的表,建立一個非彙集索引,最後插入80000條記錄。優化
1 -- Create a new table 2 CREATE TABLE Customers 3 ( 4 CustomerID INT NOT NULL PRIMARY KEY CLUSTERED, 5 CustomerName CHAR(10) NOT NULL, 6 CustomerAddress CHAR(10) NOT NULL, 7 Comments CHAR(5) NOT NULL, 8 Value INT NOT NULL 9 ) 10 GO 11 12 -- Create a supporting new Non-Clustered Index. 13 CREATE UNIQUE NONCLUSTERED INDEX idx_Test ON Customers(Value) 14 GO 15 16 -- Insert 80000 records 17 DECLARE @i INT = 1 18 WHILE (@i <= 80000) 19 BEGIN 20 INSERT INTO Customers VALUES 21 ( 22 @i, 23 CAST(@i AS CHAR(10)), 24 CAST(@i AS CHAR(10)), 25 CAST(@i AS CHAR(5)), 26 @i 27 ) 28 29 SET @i += 1 30 END 31 GO
爲了訪問咱們的表,我額建立了一個簡單的存儲過程,傳入value值做爲過濾謂語。spa
1 -- Create a simple stored procedure to retrieve the data 2 CREATE PROCEDURE RetrieveCustomers 3 ( 4 @Value INT 5 ) 6 AS 7 BEGIN 8 SELECT * FROM Customers 9 WHERE Value < @Value 10 END 11 GO
如今我用80000的參數值來執行存儲過程。設計
1 -- Execute the stored procedure. 2 -- This generates an execution plan with a Key Lookup (Clustered). 3 EXEC RetrieveCustomers 80000 4 GO
如今當你查看實際的執行計劃時,你會看到查詢優化器已經選擇了有419個邏輯讀的彙集索引掃描運算符。SQL Server並無使用非彙集索引,由於這樣沒有意義,因爲臨界點。這個查詢結果並無選擇性。code
如今假設SQL Server發生了些事情(例如重啓,故障轉移),SQL Server忽略已經緩存的計劃,這裏我經過執行DBCC FREEPROCCACHE從計劃緩存裏抹掉每一個緩存的計劃來模擬SQL Server重啓(不要在生產環境裏使用!)。orm
1 -- Get rid of the cached execution plan... 2 DBCC FREEPROCCACHE 3 GO
如今有人再次調用你的存儲過程,此次輸入參數值是1。此次執行計劃不同,由於如今在執行計劃裏你會有書籤查找。SQL Server估計行數是1,在非彙集索引裏沒有找到任何行。所以與非彙集索引查找結合的書籤查找纔有意義,由於這個查詢是有選擇性的。
如今我再執行用80000參數值的查詢。
1 -- Execute the stored procedure 2 EXEC RetrieveCustomers 1 3 GO 4 5 -- Execute the stored procedure again 6 -- This introduces now a plan regression, because now we get a Clustered Index Scan 7 -- instead of the Key Lookup (Clustered). 8 EXEC RetrieveCustomers 80000 9 GO
當你再次看STATISTICS IO的輸出,你會看到這個查詢如今產生了160139個邏輯讀——剛纔的查詢只有419個邏輯讀。這個時候DBA的手機就會響起,性能問題。但今天咱們要不一樣的方式解決——使用剛纔啓用的查詢存儲。
當你再次看實際的執行計劃,在你面前你會看到有一個計劃迴歸,由於SQL Server剛重用了書籤查找的的計劃緩存。剛纔你有彙集索引掃描運算符的執行計劃。這是SQL Server裏參數嗅探的反作用。
讓咱們經過查詢存儲來詳細瞭解這個問題。在SSMS裏的對象資源管理器裏,SQL Server 2016提供了一個新的結點叫查詢存儲,這裏你會看到一些報表。
【前幾個資源使用查詢】向你展現了最昂貴的查詢,基於你選擇的維度。這裏切換到【邏輯讀取次數】。
這裏在你面前有一些查詢。最昂貴的查詢生成了近500000個邏輯讀。這是咱們的初始語句。這已是第一個WOW效果的的查詢存儲:SQL Server重啓後,查詢存儲的數據仍是存在的!第2個是你存儲過程裏的SELECT語句。在查詢存儲裏每一個捕獲的查詢都有一個標示號——這裏是7。最後當你看報告的右邊,你會看這個查詢的不一樣執行計劃。
如你所見,查詢存儲捕獲了2個不一樣的執行計劃,一個ID是7,一個ID是8。當你點擊計劃ID時,SQL Server會在報表的最下面爲你顯示估計的執行計劃。
計劃8是彙集索引掃描,計劃7是書籤查找。如你所見,使用查詢存儲分析計劃迴歸很是簡單。但你如今還沒結束。你如今能夠對指定的查詢強制執行計劃。 如今你知道包含彙集索引掃描的執行計劃有更好的性能。所以如今你能夠經過點擊【強制執行計劃】強制查詢7使用執行計劃。
如今當你執行存儲過程(用80000的輸入參數值),在執行計劃裏你能夠看到彙集索引掃描,執行計劃只生成419個邏輯讀——很簡單,是否是?絕對不是!!!!
微軟告訴咱們只給修正SQL Server性能相關的「新方式」。你只是強制了特定的計劃,一切都還好。這個方法有個大的問題,由於性能問題的根源並無解決!這個問題的關鍵是由於書籤查找計劃沒有穩定性。取決於首次執行計劃默認的輸入值,執行計劃所以就被不斷重用。
一般我會建議調整下你的索引設計,建立一個覆蓋索引來保證計劃的穩定性。但強制特定執行計劃只是臨時解決問題——你仍是要修正你問題的根源。
不要誤解我:SQL Server 2016裏的查詢存儲功能很棒,能夠幫你更容易理解計劃迴歸。它也會幫你「臨時」強制特定的執行計劃。但性能調優的目標仍是同樣:你要找到問題根源,嘗試解決問題——不要在外面晃盪!
感謝關注!