消失的 Execution Plan
當使用 Dynamic SQL 動態傳遞 TABLE name(資料表名稱) 變數
無論是使用 sp_executesql 或 EXEC,都要等到 執行階段,動態傳入資料表名稱 後,Query Optimizer 方能依據傳入的資料表資訊,分析 SQL 陳述式,判斷出取得所需資料的最有效方式。
影響
- Display Estimated Execution Plan 無法正確顯示。
- Plan Cache 無法直接觀察。
看不到 實際執行的 Execution Plan!
Dynamic SQL: sp_executesql, Pass dynamic TABLE name variable,
動態傳遞 資料表名稱 變數
01. 建立使用 Dynamic SQL: sp_executesql 動態傳遞 資料表變數 的 Stored Procedure。
- @tsql
- 需要執行的 SQL 陳述式或批次,必須使用 Unicode 字串。
- @Parms
- N'@TransactionDate datetime',此為參數定義的 Unicode 字串。,由 參數名稱 和 資料類型 組成。
使用 QUOTENAME()
- 附加傳回 [] 分隔符號,以便讓 輸入字串 成為有效的 [SQL Server] 分隔識別碼。
- 限制為 128 個 Unicode 字元。
- 用於處理 SQL Injection(資料隱碼) 問題, 驗證 Dynamic SQL 使用的所有變數都已正確處理。
-- figure 01_Proc_pr_DynamicTable
02. 點選 Display Estimated Execution Plan,嘗試檢視 Execution Plan。
- 檢視 使用 Dynamic SQL: sp_executesql 動態傳遞 資料表名稱 變數 的 Stored Procedure。
-- figure 02_Display Estimated Execution Plan
03. 消失的 Execution Plan!
無可分析!
因為要等到 執行階段,動態傳入資料表名稱 後,Query Optimizer 方能依據傳入的資料表資訊,分析 SQL 陳述式,判斷出取得所需資料的最有效方式。
-- figure 03_Display Estimated Execution Plan
04. 改用 Include Actual Execution Plan。
- 點選 Include Actual Execution Plan,執行 2 段 SQL 陳述式。
-- figure 04_Exec_Proc_Include Actual Execution Plan
05. 檢視 Stored Procedure 執行結果。
資料表變數: TransactionHistory
- 回傳 0 筆資料列。
資料表變數: TransactionHistoryArchive
- 回傳 215 筆資料列。
-- figure 05_Exec_Proc_Include Actual Execution Plan
06. 檢視 Execution Plan。
資料表變數: TransactionHistory
- 回傳 0 筆資料列。
- Clustered Index Scan。
- 有 Missing Index。
資料表變數: TransactionHistoryArchive
- 回傳 215 筆資料列。
- Clustered Index Scan。
- 有 Missing Index。
-- figure 06_View_Actual_Execution_Plan
07. 檢視 Plan Cache
1st: CREATE PROC pr_DynamicTable @tbname sysname, ...
- LastElapsedTime, last_logical_reads 等,都是 NULL。
- ExecutionCount 累積到 2。
- ObjType: Proc。
2nd 與 3rd: SET STATISTICS XML OFF 與 SET STATISTICS XML ON
- 這是因為啟用了 Include Actual Execution Plan 功能,SQL Server 使用 XML 文件格式來產生 SQL 陳述式執行狀況的詳細資料。
- LastElapsedTime, last_logical_reads 等,都是 NULL。
- ObjType: Prepared。
4th: (@TransactionDate datetime) SELECT [TransactionID],..
- LastElapsedTime, last_logical_reads 等,都有顯示出資料。
- 這是 實際執行 SQL 陳述式後產生的 效能資料。
- 點選 [QueryPlan],可以看到 實際執行的 Execution Plan。
- ObjType: Prepared。
5th: (@TransactionDate datetime) SELECT [TransactionID],..
- LastElapsedTime, last_logical_reads 等,都有顯示出資料。
- 這是 實際執行 SQL 陳述式後產生的 效能資料。
- 點選 [QueryPlan],可以看到 實際執行的 Execution Plan。
- ObjType: Prepared。
-- figure 07_Plan_Cache
08. 可以看到 [ParameterList] 與 [QueryPlan] 都有實際資料。
-- figure 08_Plan_Cache
消失的 Execution Plan
- 看不到 實際執行的 Execution Plan!
-- figure 09_1 Missing Execution Plan
Execution Plan 藏在這裡 ...
-- figure 09_2
09. 重複 執行多次 Stored Procedure。
- 資料表變數: TransactionHistory,再執行 10 次。
- 資料表變數: TransactionHistoryArchive,再執行 20 次。
-- figure 11_Run many times
10. 觀察 Plan Cache。
- 1st: ExecutionCount 累積到 32。
- ObjType: Proc。
- 2nd: 資料表變數: TransactionHistoryArchive,ExecutionCount 累積到 21。
- ObjType: Prepared。
- 3rd: 資料表變數: TransactionHistory,ExecutionCount 累積到 11。
- ObjType: Prepared。
-- figure 12_Plan_Cache
11. 輸入更多不同的 Parameter value
資料表變數: TransactionHistory
- 但日期更換為: 2013-06-10, 2013-06-11, 2013-06-12
資料表變數: TransactionHistoryArchive
- 但日期更換為: 2013-06-10, 2013-06-11, 2013-06-12
-- figure 13_ Different parameter value with the same table
12. 觀察 Plan Cache。
- 1st: ExecutionCount 累積到 38。
- ObjType: Proc。
- 2nd: 資料表變數: TransactionHistoryArchive,ExecutionCount 累積到 24。
- 使用相同資料表,雖然 Parameter value不同,但可以 Reuse Execution Plan。
- ObjType: Prepared。
- 3rd: 資料表變數: TransactionHistory,ExecutionCount 累積到 14。
- 使用相同資料表,雖然 Parameter value不同,但可以 Reuse Execution Plan。
- ObjType: Prepared。
Dynamic SQL: sp_executesql
- ObjType: Prepared
- 使用 Parameter Sniffing 機制來 Re-Use Execution Plan。
- 必須面對 Parameter Sniffing 所帶來的 Side Effects。
-- figure 14_Plan Cache
Dynamic SQL: EXEC, Pass dynamic TABLE name variable
動態傳遞 資料表名稱 變數
改用 EXEC。
01. 建立使用 Dynamic SQL: EXEC 動態傳遞 資料表變數 的 Stored Procedure。
- @tsql: 需要執行的 SQL 陳述式或批次。
-- figure 71_Proc_Pass dynamic TABLE name to EXEC
02. 點選 Display Estimated Execution Plan,消失的 Execution Plan,無可分析!
- 因為要等到 執行階段,動態傳入資料表名稱 後,Query Optimizer 方能依據傳入的資料表資訊,分析 SQL 陳述式,判斷出取得所需資料的最有效方式。
03. 改用 Include Actual Execution Plan。
點選 Include Actual Execution Plan,執行以下多段 SQL 陳述式。
04. 觀察 Execution Plan。
1st: CREATE PROC pr_DynamicTable @tbname sysname, ...
- LastElapsedTime, last_logical_reads 等,都是 NULL。
- ExecutionCount 累積到 2。
- ObjType: Proc。
2nd: 是重複執行 'TransactionHistoryArchive', '2013-07-30';
- ExecutionCount 累積到 21。
- ObjType: Adhoc。
3rd: 是重複執行 'TransactionHistory', '2013-07-30';
- ExecutionCount 累積到 11。
- ObjType: Adhoc。
4th~9th: 資料表變數: TransactionHistory 或是 TransactionHistoryArchive
- 但日期更換為: 2013-06-10, 2013-06-11, 2013-06-12
- ExecutionCount 都是 1
- 相同資料表,但 Parameter value不同,產生的 hash value 也不相同。
- 導致無法 Reuse Execution Plan。
- ObjType: Adhoc。
-- figure 73_Plan_Cache
05. 觀察 Execution Plan,檢視 SQL statement 的 Parameter value。
- Parameter value不同,產生的 hash value 也不相同,導致無法 Reuse Execution Plan。
Dynamic SQL: EXEC
採取比對 hash 值
- 相同,就能重複使用 Execution Plan。
- 不同,產生新的 Execution Plan。
- ObjType: Adhoc。
-- figure 74_Plan_Cache
QUOTENAME()
功能:傳回 Unicode 字串,且附加了分隔符號,以便使輸入字串成為有效的 [SQL Server] 分隔識別碼。
限制為 128 個字元。 大於 128 個字元的輸入會傳回 NULL。
語法
QUOTENAME ( 'character_string' [ , 'quote_character' ] )
'quote_character'
這是用來當做分隔符號的單字元字串。
它可以是單引號 ( ' )、左或右方括弧 ( [] )、雙引號 ( " )、左或右括弧 ( () )、
大於或小於符號 ( >< )、左或右大括弧 ( {} ) 或反勾號 ( ` )。
如果提供了無法接受的字元,則會傳回 NULL。
如果未指定 quote_character,就會使用方括號。
用於面對 SQL Injection(資料隱碼),建議搭配 QUOTENAME() 或 REPLACE(), 來 驗證 Dynamic SQL 使用的所有變數都已正確處理。
Sample Code
20180917_Dynamic SQL Pass dynamic TABLE name variable
https://drive.google.com/drive/folders/0B9PQZW3M2F40OTk3YjI2NTEtNjUxZS00MjNjLWFkYWItMzg4ZDhhMGIwMTZl?usp=sharing
Reference
[Case Study] Parameter Sniffing with OR Operator issue, 當 Parameter Sniffing 遇上 OR 運算子 (1)
http://sharedderrick.blogspot.com/2018/08/case-study-parameter-sniffing-with-or.html
Parameter Sniffing: Dynamic SQL, sp_executesql and EXEC (Parameter Sniffing 與 動態 SQL)
http://sharedderrick.blogspot.com/2018/08/parameter-sniffing-dynamic-sql.html
QUOTENAME (Transact-SQL)
https://docs.microsoft.com/zh-tw/sql/t-sql/functions/quotename-transact-sql?view=sql-server-2017
SQL 資料隱碼
https://docs.microsoft.com/zh-tw/sql/relational-databases/security/sql-injection?view=sql-server-2017