如何用參數化SQL語句污染你的計劃緩存
你的SQL語句的參數化總是個好想法。使用參數化SQL語句你不會污染你的計劃緩存——錯!!!在這篇文章里我想向你展示下用參數化SQL語句就可以污染你的計劃緩存,這是非常簡單的!
ADO.NET-AddWithValue
ADO.NET是實現像SQL Server關系數據庫數據訪問的.NET框架的組成——有一些嚴重的副作用。不要誤解我——只要你正確使用,ADO.NET一直很棒。你馬上就會看到,它很容易被錯誤使用。我們來看下面實現SQL語句執行的C#代碼。
- for (int i = 1; i <= 100; i++)
- {
- val += i.ToString();
- cmd = new SqlCommand(
- "SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber = @CarrierTrackingNumber",
- cnn);
- cmd.Parameters.AddWithValue("@CarrierTrackingNumber", val);
- SqlDataReader reader = cmd.ExecuteReader();
- reader.Close();
- }
我們是聰明的開發者,因此SQL語句本身被參數化,因為ADO.NET框架是地球上最棒的框架,我們使用System.Data.SqlClient.SqlParameterCollection類的AddWithValue方法來提供實際的參數值。我在WHLIE循環里運行那個SQL語句100次,總用不同長度賦予參數值。在Sales.SalesOrderDetail表里CarrierTrackingNumber列定義為NVARCHAR(25)。因此我們可以在基于我們提供的不同字符長度上有上至25個不同數據類型的參數。現在讓我們檢查下我們SQL語句執行后的計劃緩存。
- 1 SELECT
- 2 st.text,
- 3 cp.*
- 4 FROM sys.dm_exec_cached_plans cp
- 5 CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
- 6 GO
現在事情變得有點瘋狂:在計劃緩存里我們存儲了100個不同的執行計劃!

對于每個可能的數據類型參數都有1個執行計劃——即使當數據類型是NVACHAR(2***ddWithValue方法非常,非常邪惡:基于你提供的參數值派生出數據類型。永遠不要使用它!
ADO.NET – SqlDbType.VarChar
因為從我們的錯誤中我們學到了,現在我們知道ADO.NET的AddWithValue方法的副作用——我們不再用它。現在讓我們重寫我們的C#程序代碼,如下所示定義一個顯示的參數數據類型:
- for (int i = 1; i <= 100; i++)
- {
- val += i.ToString();
- cmd = new SqlCommand(
- "SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber = @CarrierTrackingNumber",
- cnn);
- cmd.Parameters.Add(new SqlParameter("@CarrierTrackingNumber", SqlDbType.VarChar));
- cmd.Parameters["@CarrierTrackingNumber"].Value = val;
- SqlDataReader reader = cmd.ExecuteReader();
- reader.Close();
- }
從代碼里你可以看到,ADO.NET現在不能派生參數數據類型了,因為我們已經指定了SqlDbType.Varchar數據類型。讓我們再次執行這個SQL語句100次并再次檢查下計劃緩存:

沒有啥改變。問題還是一樣:在計劃緩存里我們還有100個不一樣的的執行計劃。現在的問題是ADO.NET只強制數據類型(SqlDbType.VarChar),但不是數據類型的"長度"。有100個不同的長度在計劃緩存里你就有100個不同的執行計劃。
如果你在你的ADO.NET代碼里顯式指定參數數據類型,你也要指定它的長度!現在我們來看下一些修正的C#代碼。
- for (int i = 1; i <= 100; i++)
- {
- val += i.ToString();
- cmd = new SqlCommand(
- "SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber = @CarrierTrackingNumber",
- cnn);
- cmd.Parameters.Add(new SqlParameter("@CarrierTrackingNumber", SqlDbType.VarChar, 100));
- cmd.Parameters["@CarrierTrackingNumber"].Value = val;
- SqlDataReader reader = cmd.ExecuteReader();
- reader.Close();
- }

小結
寓意:ADO.NET是個很棒的數據訪問框架,它提供你有用的功能(例如AddWithValue方法),當從SQL Server角度來說你真的要考慮下你在做什么。當你使用參數化SQL語句時,你要盡量顯式:你必須地冠以參數值的實際數據類型,還有你想要的獲得數據類型長度。
感謝關注!
注:此文章為WoodyTu學習MS SQL技術,收集整理相關文檔撰寫,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出此文鏈接!