在之前的隨筆中我提到過參數嗅探,這是非常重要的概念。下麵我們深入的研究一下參數嗅探… 首先我們知道批處理可以是參數化的或者非參數化。參數化的批處理計劃有兩種類型:“Prepared” 或者“Proc”。前者對應帶有至少一個參數的sys.sp_executesql的執行,並且從T-SQL批處理,或者應 ...
在之前的隨筆中我提到過參數嗅探,這是非常重要的概念。下麵我們深入的研究一下參數嗅探…
首先我們知道批處理可以是參數化的或者非參數化。參數化的批處理計劃有兩種類型:“Prepared” 或者“Proc”。前者對應帶有至少一個參數的sys.sp_executesql的執行,並且從T-SQL批處理,或者應用程式通過ADO.NET等直接被執行的。後者的執行計劃對應一個存儲過程。
參數嗅探在這兩種類型中是完全相同的。它的行為在兩種計劃中是完全一樣的。因此我們這裡不去討論類型,只關心參數化批處理本身的作用。
什麼是參數嗅探?
當批處理包含一個或者多個參數時並且它需要優化(例如因為沒有該批處理執行計劃緩存,或者只有不可用的計劃),優化器知道參數的值。意味著優化器可以使用參數值去估計計劃中每個步驟返回的行數。就好像參數的值被硬編碼到批處理的文本中。這就是參數嗅探。
這是很有用的,因為如果優化器不知道參數的值,它將被迫去猜測返回的行數。基於平均統計和其他使用的元數據來儘可能準確地猜測,但是大多數時候仍然與真實行數相去甚遠。錯誤的估計導致選擇低效的執行計劃並導致很差的性能。
例如,一個批處理第一次被執行,首先編譯,因為對應計劃在計劃緩存中沒有。在編譯時,由於有參數嗅探,參數的值將被用來生成執行計劃。當計劃被創建時,計劃被放在緩存中用來重用。下次相同的批處理被執行時,雖然有可能用了不同的參數,但是緩存中的計劃仍將被重用。當然,第二次執行的結果將基於參數的值。但是執行第二次的計劃是與第一次一樣的,這個計劃就是來自於第一次執行的參數。
如上圖。在參數化批處理的實際執行計劃的圖形表示中,查看最外層的操作符屬性(通常是一個select操作符),然後找到“Parameter List”屬性。展開屬性時,將會看到每個參數編譯時和運行時的值。編譯時的值就是參數嗅探用來生成計劃的參數。運行時的值是實際在指定計劃中的參數。
實際上,第二次執行可能是性能很差的,因為優化器在兩次生成計劃時估計返回數據的行數可能是相差大的。這裡純粹是運氣,沒有更好地方式。執行計劃被參數的值決定,而我們不能控制它,因為不知道編譯何時放生。如果計劃對於大多數執行時很高效的,那麼一切ok,但是如果它不是呢?假使用戶用一個很少使用的參數來執行存儲過程,這個參數值產生一個執行計劃,並且對於指定該值作為參數的執行是非常高效的。但是其他參數時將會表現很糟糕。
因此問題來了:參數嗅探是好還是不好?
一如往常,答案就是:“看情況”。這取決於數據的分佈。讓我們看一下之前用的存儲過程:
CREATE PROCEDURE Marketing.usp_CustomersByCountry ( @Country AS NCHAR(2) ) AS SELECT Id , Name , LastPurchaseDate FROM Marketing.Customers WHERE Country = @Country; GO
這種情況下,使用參數@Country,來過濾行customers表的行數。如果大多數國家有差不多的行數,而且大多數執行使用了這些國家,那麼參數嗅探是很好的事情,因為大多數情況執行計劃是適用的,並且比不帶參數嗅探的計劃要好(未知參數)。另一方面,如果國家的值的分佈不是均勻的,那麼一個國家編碼的參數很有可能對於其他國家的查詢計劃就是一個糟糕的選擇,此時參數嗅探就是不好的事情了。
那麼我們在參數嗅探是否有益這件事情上能做什麼?下一章將介紹如何高效的使用參數嗅探。