在上篇文章中咱們談到了查詢優化器和執行計劃緩存的關係,以及其兩者之間的衝突。本篇文章中,咱們會主要闡述執行計劃緩存常見的問題以及一些解決辦法。html
上篇文章中提到了查詢優化器解析語句的過程,當將計劃緩存考慮在內時,首先須要查看計劃緩存中是否已經有語句的緩存,若是沒有,纔會執行編譯過程,若是存在則直接利用編譯好的執行計劃。所以,完整的過程如圖1所示。sql
圖1.將計劃緩存考慮在內的過程數據庫
圖1中咱們能夠看到,其中有一步須要在緩存中找到計劃的過程。所以不難猜出,只要是這一類查找,必定跑不了散列(Hash)的數據結構。經過sys.dm_os_memory_cache_hash_tables這個DMV能夠找到有關該Hash表的一些信息,如圖2所示。這裏值得注意的是,當執行計劃過多致使散列後的對象在同一個Bucket過多時,則須要額外的Bucket,所以可能會致使查找計劃緩存效率低下。解決辦法是儘可能減小在計劃緩存中的計劃個數,咱們會在本文後面討論到。緩存
圖2.有關存儲計劃緩存的HashTable的相關信息數據結構
當出現這類問題時,咱們能夠在buckets_avg_scan_miss_length列看出問題。這類狀況在緩存命中率(SQL Server: Plan Cache-Cache Hit Ratio)比較高,但編譯時間過長時能夠做爲考慮對象。ide
查詢計劃的惟一標識是查詢語句自己,但假設語句的主體同樣,而僅僅是查詢條件謂詞不同,那在執行計劃中算1個執行計劃仍是兩個執行計劃呢?It’s Depends。性能
假設下面兩個語句,如圖3所示。優化
圖3.僅僅謂詞條件不同的兩個語句ui
雖然執行計劃同樣,可是在執行計劃緩存中卻會保留兩份執行計劃,如圖4所示。spa
圖4.同一個語句,不一樣條件,有兩份不一樣的執行計劃緩存
咱們知道,執行計劃緩存依靠查詢語句自己來判別緩存,所以上面兩個語句在執行計劃緩存中就被視爲兩個不一樣的語句。那麼解決該問題的手段就是使得執行計劃緩存中的查詢語句如出一轍。
參數化
使得僅僅是某些參數不一樣,而查詢自己相同的語句能夠複用,就是參數化的意義所在。好比說圖3中的語句,若是咱們啓用了數據庫的強制參數化,或是使用存儲過程等。SQL Server會將這些語句強制參數話,好比說咱們根據圖5修改了數據庫層級的選項。
圖5.數據庫層級的選項
此時咱們再來執行圖3中的兩條語句,經過查詢執行計劃緩存,咱們發現變量部分被參數化了,從而在計劃緩存中的語句變得一致,如圖6所示,從而能夠複用.
圖6.參數話以後的查詢語句
可是,強制參數會引發一些問題,查詢優化器不少時候就沒法根據統計信息最優化一些具體的查詢,好比說不能應用一些索引或者該掃描的時候卻查找。所產生的負面影響在上篇文章中已經說過,這裏就不細說了。
所以對於上面的問題能夠有幾種解決辦法。
在具體的狀況下,參數化有些時候是好的,但有些時候倒是性能問題的罪魁禍首,下面咱們來看幾種平衡這二者之間關係的手段。
使用RECOMPILE
當查詢中,不許確的執行計劃的成本要高於編譯的成本時,在存儲過程當中使用RECOMPILE選項或是在即席查詢中使用RECOMPILE提示使得每次查詢都會從新生成執行計劃,該參數會使得生成的執行計劃不會被插入到執行計劃緩存中。對於OLAP類查詢來講,不許確的執行計劃所耗費的成本每每高於編譯成本太多,因此能夠考慮該參數或選項,您能夠如代碼清單1中的查詢所示這樣使用Hint。
SELECT * FROM Sales.Customer
WHERE CustomerID>20000 AND TerritoryID = 4
OPTION (recompile)
代碼清單1.使用Recompile
除去咱們能夠手動提示SQL Server重編譯以外,SQL Server也會在下列條件下自動重編譯:
使用Optimize For參數
RECOMPILE方式提供了徹底不使用計劃緩存的節奏。但有些時候,特性謂語的執行計劃被使用的次數h更多,好比說,僅僅那些謂語條件產生大量返回結果集的參數編譯,咱們能夠考慮Optimize For參數。好比咱們來看代碼清單2。
DECLARE @vari INT
SET @vari=4
SELECT * FROM Sales.Customer
WHERE CustomerID>20000 AND TerritoryID = @vari
OPTION (OPTIMIZE FOR (@vari=4))
代碼清單2.使用OPTIMIZE FOR提示
使用了該參數會使得緩存的執行計劃按照OPTIMIZE FOR後面的謂語條件來生成並緩存執行計劃,這也可能形成不在該參數中的查詢效率低下,可是該參數是咱們選擇的,所以一般咱們知道哪些謂語條件會被使用的多一些。
另外,自SQL Server 2008開始多了一個OPTIMIZE FOR UNKNOWN參數,這使得在優化查詢的過程當中探測做爲謂語條件的局部參數的值,而不是根據局部變量的初始值去探測統計信息。
在存儲過程當中使用局部變量代替存儲過程參數
在存儲過程當中不使用過程參數,而是使用局部變量至關於直接禁用參數嗅探。畢竟,局部變量的值只有在運行時才能知道,在執行計劃被查詢優化器編譯時是沒法知道該值的,所以強迫查詢分析器使用條件列的平均值進行估計。
雖然這種方式使得參數估計變得很是不許確,可是會變得很是穩定,畢竟統計信息不會變動的過於頻繁。該方式不被推薦,若是可能,儘可能使用Optimizer的方式。
代碼清單3展現了這種方式。
CREATE PROC TestForLocalVari
@v INT
AS
DECLARE @vari INT
SET @vari=@v
SELECT * FROM Sales.Customer
WHERE CustomerID>20000 AND TerritoryID = @vari
代碼清單3.直接引用局部變量,而不是存儲過程參數
強制參數化
在本篇文章的前面已經提到過了強制參數化,這裏就再也不提了。
使用計劃指導
在某些狀況下,咱們的環境不容許咱們直接修改SQL語句,好比所不但願破壞代碼的邏輯性或是應用程序是第三方開發,所以不管是加HINT或參數都變得不現實。此時咱們可使用計劃指導。
計劃指導使得查詢語句在由客戶端應用程序扔到SQL Server的時候,SQL Server對其加上提示或選項,好比說經過代碼清單4能夠看到一個計劃指導的例子。
EXEC sp_create_plan_guide N'MyPlanGuide1',
@stmt=N'SELECT * FROM Sales.Customer WHERE CustomerID>20000 AND TerritoryID=@vari',
@type=N'sql',
@module_or_batch=NULL,
@params=N'@vari int',
@hints=N'OPTION (RECOMPILE)'
代碼清單4.對咱們前面的查詢設置計劃指導
當加入了計劃指導後,當批處理到達SQL Server時,在查找匹配的計劃緩存時也會去找是否有計劃指導和其相匹配。若是匹配,則應用計劃指導中的提示或選項。這裏要注意的是,這裏@stmt參數必須和查詢語句中的一句如出一轍,差一個空格都會被認爲不匹配。
PARAMETERIZATION SIMPLE
當咱們在數據庫層級啓用了強制參數化時,對於特定語句,咱們卻不想啓用強制參數化,咱們可使用PARAMETERIZATION SIMPLE選項,如代碼清單5所示。
DECLARE @stmt NVARCHAR(MAX)
DECLARE @params NVARCHAR(MAX)
EXEC sp_get_query_template N'SELECT * FROM Sales.Customer WHERE CustomerID>20000 AND TerritoryID=2',
@stmt OUTPUT, @params OUTPUT
PRINT @stmt
PRINT @params
EXEC sp_create_plan_guide N'MyTemplatePlanGuide', @stmt, N'TEMPLATE', NULL,
@params, N'OPTION(PARAMETERIZATION SIMPLE)'
代碼清單5.經過計劃指南對單條語句應用簡單參數化
執行計劃緩存但願儘可能重用執行計劃,這會減小編譯所消耗的CPU和執行緩存所消耗的內存。而查詢優化器但願儘可能生成更精準的執行計劃,這勢必會形成大量的執行計劃,這不只僅可能引發重編譯大量消耗CPU,還會形成內存壓力,甚至當執行計劃緩存過多超過BUCKET的限制時,在緩存中匹配執行計劃的步驟也會消耗更多的時間。
所以利用本篇文章中所述的方法基於實際的狀況平衡二者之間的關係,就變得很是重要。