幾天前,我寫了篇SQL Server里簡單參數化的痛苦。今天我想繼續這個話題,談下SQL Server里強制參數化(Forced Parameterization)。
強制參數化(Forced Parameterization)
在SQL Server里簡單參數化有很多限制,如果你的SQL語句包含下列任意它不會發生:
- JOIN
- IN
- BULK INSERT
- UNION
- INTO
- DISTINCT
- TOP
- GROUP BY
- HAVING
- COMPUTE
- Sub Queries
如果你還想讓SQL Server進行自動參數化,你可以啟用在數據庫層啟用強制參數化:
1 -- Let's now activate Forced Parameterization on the AdventureWorks2012 database 2 ALTER DATABASE AdventureWorks2012 SET PARAMETERIZATION FORCED 3 GO
在這個情況下,SQL Server總會自動參數化你的SQL語句除掉以下情況:
- INSERT … EXECUTE
- Prepared SQL Statements
- RECOMPILE
- COMPUTE
為什么強制參數化總不是個好選擇
現在讓我們看下SQL Server里的強制參數化。對於AdventureWorks2012數據庫,最后的代碼已經將它啟用強制參數化了。下一步我創建Sales.SalesOrderHeader表的副本,並將數據修正,這樣的話在CustomerID列我們會有非線性的數據分布。另外我在那一列也創建了非聚集索引。
1 -- Create a copy from the Sales.SalesOrderHeader table 2 SELECT * INTO Sales.SalesOrderHeader2 FROM Sales.SalesOrderHeader 3 GO 4 5 -- Create a Non-Clustered Index on the CustomerID column 6 CREATE NONCLUSTERED INDEX idx_CustomerID ON Sales.SalesOrderHeader2(CustomerID) 7 GO 8 9 -- "Patch" the data in some way, so that the content of the column "CustomerID" is not evenly distributed across the whole table 10 UPDATE Sales.SalesOrderHeader2 11 SET CustomerID = 29675 12 WHERE SalesOrderID < 60000 13 GO
從下圖你可以看到:ID為29675的客戶有大量的訂單,其它客戶只有一些訂單:
1 SELECT CustomerID,COUNT(SalesOrderID) SaleCount FROM Sales.SalesOrderHeader2 2 GROUP BY CustomerID ORDER BY 2 DESC
在下一步里我執行一個返回CustomerID為22943的所有記錄——只有3條記錄。因為查詢在臨界點前,SQL Server選擇了有書簽查找的執行計划。查詢合計生成了3個邏輯讀。因為我們對AdventureWorks2012數據庫啟用了強制參數化,對這個語句SQL Server也會自動參數化,因此執行計划會被后續的查詢重用。
1 -- 3 Logical Reads 2 SELECT * FROM Sales.SalesOrderHeader2 3 WHERE CustomerID = 22943 -- Index Seek, returns 1 record 4 GO
我們再來運行另一個查詢,返回所有CustomerID為29675的記錄。在這個情況下查詢返回16343條記錄。當你再次看執行計划時,你會看到查詢重用了剛才查詢的執行計划。
1 SELECT * FROM Sales.SalesOrderHeader2 2 WHERE CustomerID = 29675 3 GO
這是對的,以為查詢自動參數化,SQL Server在計划緩存里找已經緩存的計划。但是重用執行計划並不安全,因為現在我們進行了書簽查找16343次——對每一行——反復執行。查詢合計生成了16415個邏輯讀。使用表掃描的話只要780個邏輯讀。
這是強制參數化的副作用。SQL Server不管你執行計划的穩定性。SQL Server值自動參數化你的SQL語句,並反復重用緩存的執行計划。不管這個執行計划有糟糕。因為這是你強制SQL Server只要做的!沒有啟用強制參數化,SQL Server從不為你自動參數化SQL語句,因為那不安全。
性能問題的根源肯定是強制參數化。這里的根源是你的執行計划包含書簽查找。因為書簽查找你就沒有計划穩定性。計划沒有穩定性是說基於你輸入參數值你會有不同的執行計划。在這個例子里有時你得到書簽查找(臨界點前),有時是表掃描(臨界點后)。
在這個情況下,如果你修改下你的索引設計,為這個查詢定義一個覆蓋非聚集索引,性能問題也會消失。這樣的話也不需要啟用強制參數化,因為使用計划穩定性SQL Server會自動參數化你的SQL語句!
小結
在數據庫級別啟用強制參數化是個非常危險的事。不管你是否有計划穩定性,SQL Server總會自動參數化你的SQL語句,並反復重用你的執行計划。因此你要知道你的執行計划的詳細情況,看看它們是否會引起性能相關的問題。
感謝關注!
參考文章:
https://www.sqlpassion.at/archive/2015/07/27/the-pain-of-forced-parameterization-in-sql-server/