在參數嗅探(Parameter Sniffing)(1/2)裏,我介紹了SQL Server裏參數嗅探的基本概念和背後的問題。如你所見,當緩存的計劃被SQL Server盲目重用時,會帶來嚴重的性能問題。今天我會向你展現下如何處理這個問題,即便用不一樣的技術克服它。html
上次咱們討論形成參數嗅探問題的根源是:在執行計劃裏,SQL 語句有時會產生書籤查找,有時會產生表/彙集索引掃描。若是你能在數據庫裏修改索引,解決這個問題的最簡單方法就是提供查詢列對應的覆蓋非彙集索引。這裏咱們就要包含書籤查找的須要列,在非彙集索引的葉子層。這樣作後,就能夠得到計劃穩定性:無論提供的輸入任何參數,查詢優化器均可以編譯一樣的執行計劃——這裏就是都會用到索引查找(非彙集索引)運算符。sql
1 DROP INDEX idx_Test ON Table1 2 CREATE NONCLUSTERED INDEX idx_Test ON Table1(Column2) INCLUDE(Column1) 3 4 SELECT * FROM dbo.Table1 WHERE Column2=1 5 SELECT * FROM dbo.Table1 WHERE Column2=2
若是你不能修改你的索引設計,能夠嘗試下面的方法:數據庫
SQL Server提供給你的第一個選項是執行計劃的重編譯。它提供2個不一樣選項給你使用:緩存
咱們經過實例詳細講解下這2個選項。下面的語句會對整個存儲過程進行重編譯:性能
1 -- Create a new stored procedure for data retrieval 2 CREATE PROCEDURE RetrieveDataR 3 ( 4 @Col2Value INT 5 ) 6 WITH RECOMPILE 7 AS 8 SELECT * FROM Table1 9 WHERE Column2 = @Col2Value 10 GO
當你執行這樣的存儲過程時,查詢優化器在每次執行前都會從新編譯存儲過程。所以你獲得的執行計劃都是基於目前輸入的參數值。做爲重編譯的反作用,你的執行計劃不會被緩存,對於一個每次都重編譯的執行計劃進行緩存是沒有意義的。當你有一個大的複雜的存儲過程在存儲過程級別使用RECOMPILE選項,這樣作就沒太大意義,由於你的整個存儲每次都重編譯,而存儲過程就是爲了編譯好進行重用,從而提升執行效率。優化
1 EXEC dbo.RetrieveDataR @Col2Value = 1 -- int 2 EXEC dbo.RetrieveDataR @Col2Value = 2 -- int
若是你的參數嗅探問題只出如今一個特定的SQL語句。那就沒有必要對整個存儲過程進行重編譯了。所以從SQL Server2005開始,提供稱爲語句級別的重編譯(Statement Level Recompilation) 。你能夠對須要重編譯的SQL語句加上RECOMPILE查詢提示而不是整個存儲過程。咱們來看下下面的代碼:this
1 -- Create a new stored procedure for data retrieval 2 CREATE PROCEDURE RetrieveDataR2 3 ( 4 @Col2Value INT 5 ) 6 AS 7 SELECT * FROM Table1 8 WHERE Column2 = @Col2Value 9 10 SELECT * FROM Table1 11 WHERE Column2 = @Col2Value 12 OPTION (RECOMPILE) 13 GO
上述例子裏的第2個SQL語句在存儲過程執行的時候都會重編譯。第1個語句在執行初始時編譯好,並生成計劃緩存作後續重用。在你不想修改數據庫的索引時,這個方法是處理參數嗅探的推薦方法。spa
1 EXEC dbo.RetrieveDataR2 @Col2Value = 2 -- int
除了存儲過程或SQL語句的重編譯查詢提示,SQL Server也提供OPTIMIZE FOR的查詢提示。用這個查詢提示你能夠告訴查詢優化器哪一個參數值下,對執行計劃執行優化,咱們看下面的例子:設計
1 -- Create a new stored procedure for data retrieval 2 CREATE PROCEDURE RetrieveDataOF 3 ( 4 @Col2Value INT 5 ) 6 AS 7 SELECT * FROM Table1 8 WHERE Column2 = @Col2Value 9 OPTION (OPTIMIZE FOR (@Col2Value = 1)) 10 GO
從存儲過程的定義中你能夠看到,SQL語句的執行計劃在參數@Col2Value值爲1的時候須要進行優化。無論你提供給這個參數的任何值,你都得到爲值1優化的編譯計劃。用這個方法你已經對SQL Server放大招了,由於查詢優化器沒別的選項——它必須爲參數值1生成優化的的執行計劃。當你知道查詢計劃須要爲指定參數進行優化時,能夠使用這個方法讓SQL Server對此參數的執行計劃進行優化。在你重啓SQL Server或執行羣集故障轉移時,就能夠預知你的執行計劃。code
爲了進一步保障這個選項的有效性,你就要熟悉你的數據分佈狀況,還有何時數據分佈狀況會改變。若是數據分佈狀況已經改變,你就要修改查詢提示,看看是否仍然合適。你不能徹底相信查詢優化器,由於你已經用OPTIMIZE FOR查詢提示重置查詢優化器的選擇。要記住這個。另外在提供OPTIMIZE FOR查詢提示的同時,SQL Server也提供OPTIMIZE FOR UNKNOWN查詢提示。若是你決定使用OPTIMIZE FOR UNKNOWN查詢提示,查詢優化器就使用表統計信息裏的密度來作參數預估。若是邏輯讀超過了臨界點,仍是會使用表/索引掃描……
在這個文章裏我向你展現在SQL Server裏處理參數嗅探問題的不一樣方式。其中形成這個問題的最多見緣由是糟糕的索引設計,形成參數值傳入後優化器在執行計劃裏選擇了書籤查找。若是這樣的執行計劃被緩存重用的話,你的I/O成本就會爆表。在生成環境中,我就看到由於這個緣由就形成100GB的邏輯讀。在SQL語句上加一個簡單的RECOMPILE查詢提示就能夠解決這個問題,查詢只會增長少許的邏輯讀。
若是你不能修改數據庫索引設計,你能夠在存儲過程或SQL語句上使用RECOMPILE查詢提示。做爲反作用編譯的計劃就不會緩存。除此外的查詢提示,SQL Server還提供OPTIMIZE FOR和OPTIMIZE FOR UNKNOWN的查詢提示。在你使用這些查詢提示時,你要對你的數據和數據分佈狀況很是熟悉,由於你在重置優化器。請慎重使用!Be always aware of this fact!
https://www.sqlpassion.at/archive/2014/10/27/parameter-sniffing-part-2/