在今天的文章里,我想談下對於即席SQL語句(ad-hoc SQL statements),SQL Server使用的簡單參數化(Simple Parameterization)的一些特性和副作用。首先,如果你的SQL語句包含這些,簡單參數化不會發生:
- JOIN
- IN
- BULK INSERT
- UNION
- INTO
- DISTINCT
- TOP
- GROUP BY
- HAVING
- COMPUTE
- Sub Queries
一般來說,如果你處理所謂的安全執行計划(Safe Execution Plan),SQL Server自動參數化你的SQL語句:不管提供的參數值,查詢總必須通向一樣的執行計划。如果你的執行計划里有書簽查找,這就是不可能的例子。因為臨界點定義了是否進行書簽查找還是全表/聚集索引掃描。
自動參數化並不那么酷!
如果SQL Server能自動參數化你的SQL語句,你還是要考慮下SQL Server引入的自動參數化SQL語句的一些副作用。我們來看一個具體的例子。下列查詢創建一個表,執行一個會被SQL Server自動參數化的簡單SQL語句。
1 -- Create a simple table 2 CREATE TABLE Orders 3 ( 4 Col1 INT IDENTITY(1, 1) PRIMARY KEY NOT NULL, 5 Price DECIMAL(18, 2) 6 ) 7 GO 8 9 -- This query gets auto parametrized, because it is a simple query with a safe (consistent) plan 10 SELECT * FROM Orders 11 WHERE Price = 5.70 12 GO 13 14 -- Analyze the Plan Cache 15 SELECT 16 st.text, 17 qs.execution_count, 18 cp.cacheobjtype, 19 cp.objtype, 20 cp.*, 21 qs.*, 22 p.* 23 FROM sys.dm_exec_cached_plans cp 24 CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) p 25 CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st 26 LEFT JOIN sys.dm_exec_query_stats qs ON qs.plan_handle = cp.plan_handle 27 WHERE st.text LIKE '%Orders%' 28 GO
然后當你查看計划緩存時,你會看到SQL Server能為你自動參數化SQL語句:
(@1 numeric(3,2))SELECT * FROM [Orders] WHERE [Price]=@1
但什么是選擇的作為參數的數據類型?最小可能的那個!在這里是NUMERIC(3,2)!如果現在你執行下列2個查詢:
1 -- Execute a slightly different query 2 SELECT * FROM Orders 3 WHERE Price = 8.70 4 GO 5 6 -- Execute a slightly different query 7 SELECT * FROM Orders 8 WHERE Price = 124.50 9 GO
SQL Server能重用為第1個使用8.7值SQL語句的參數化SQL語句的執行計划。但用124.50值的第2個SQL語句呢?對於這個SQL語句緩存的計划不能被重用,因為124.50值不符合NUMERIC(3,2)。在這個情況下,SQL Server用NUMERIC(5,2)數據類型生成你SQL語句的新參數化版本。你剛用你的SQL語句的額外的參數化版本污染了你的計划緩存!當你執行下列語句會變得更糟:
-- Execute a slightly different query SELECT * FROM Orders WHERE Price = 1204.50 GO
這個會再次給你新的用NUMERIC(6,2)數據類型的新參數化版本——計划緩存里另一個版本!當我展示這個行為的時候,很多人都建議我應該用逆序來執行剛才的SQL語句。我們通過首先清空計划緩存來試下。
1 -- Clear the Plan Cache 2 DBCC FREEPROCCACHE 3 GO 4 5 -- Execute a slightly different query 6 SELECT * FROM Orders 7 WHERE Price = 1204.50 8 GO 9 10 -- Execute a slightly different query 11 SELECT * FROM Orders 12 WHERE Price = 124.50 13 GO 14 15 -- Execute a slightly different query 16 SELECT * FROM Orders 17 WHERE Price = 8.70 18 GO
然后當你看計划緩存時,沒有任何改變:SQL Server還生成了3個不同的參數化SQL語句——每次都用最小可能的數據類型。
你怎么做沒有一點關系,即你執行你SQL語句的順序:在自動參數化期間,SQL Server總會選擇最小可能的數據類型。當你依賴SQL Server這個特性時,好好考慮下。
VARCHAR如何呢?SQL Server自動參數化包含字符值(例如VARCHAR)的SQL語句時,事情會好點。假設有下列表定義和下列2個查詢:
1 -- Create another table to demonstrate this problem 2 CREATE TABLE Orders3 3 ( 4 Col1 INT IDENTITY(1, 1) PRIMARY KEY NOT NULL, 5 Col2 VARCHAR(100) 6 ) 7 GO 8 9 -- Clears the Plan Cache 10 DBCC FREEPROCCACHE 11 GO 12 13 -- A VARCHAR/CHAR column is always auto parametrized to a VARCHAR(8000) 14 SELECT * FROM Orders3 15 WHERE Col2 = 'Woody' 16 GO 17 18 -- A VARCHAR column is always auto parametrized to a VARCHAR(8000) 19 SELECT * FROM Orders3 20 WHERE Col2 = 'Tu' 21 GO
在這個情況下,SQL Server用VARCHAR(8000)生成1個自動參數化SQL語句——最大可能的數據類型。從剛才例子里,這是你所期待的行為。有時SQL Server好事壞事同時做……
小結
當你和簡單SQL語句打交道時,自動參數化可以非常棒。但如你在這個文章里所見,你要知道SQL Server引入的副作用。另外SQL Server的簡單參數化特性還會提供你強制參數化(Forced Parameterization)功能,這個我會在以后的文章里介紹。
感謝關注!
參考文章:
https://www.sqlpassion.at/archive/2015/04/27/the-pain-of-simple-parameterization-in-sql-server/