上網忽快忽慢?可能是受到來自駭客的網路攻擊。
但 Stored Procedure 執行速度 忽快忽慢?怎麼會這樣?
這可能是受到 Parameter Sniffing 的影響。
了解 Parameter Sniffing 功能,將有助於開發高效能 Stored Procedure,而 SQL Server: Parameter Sniffing 類似於 Oracle: Bind Variables。本文將先討論什麼是 Parameter Sniffing,以及其優缺點。後續文章中,將陸續介紹其解決方案與應用方式。
Parameter Sniffing (參數探測)
「Parameter Sniffing」
- 是指 SQL Server 在 compile 編譯 或 recompile 重新編譯期間,執行「sniffs 探查」目前的參數值。
- 然後將它傳遞給 Query Optimizer,以便可用來產生可能 更有效率 之 Execution Plan 的程序。
若每次執行 SQL 陳述式,都要重新 recompile SQL 陳述式,將額外耗損掉系統資源。為了節省所耗用的系統資源,SQL Server 採取 reuse 重複使用 Execution Plan 的方式。
在 編譯 或 重新編譯 期間,會探查下列批次類型的參數值:
- Stored procedures
- 透過 sp_executesql 提交的查詢
- Prepared queries 準備查詢
- 第 1 次,執行 Stored procedures,Query Optimizer 使用 Parameter Sniffing 功能 探查,並建立適合的 Execution Plan。
- 第2次,Reuse 同一份 Execution Plan。
Pro and Cons of Parameter Sniffing
Advantage
|
Disadvantage
|
|
|
Summary
- 使用 Parameter Sniffing 可節省 recompile 的系統資源。
- 但也可能 reuse 到不適合的 Execution Plan,使得執行 效能 更糟,耗用更多的系統資源!
- 甚至使得 Estimated Subtree Cost 不具備使用價值。
Compare Reuse Good and Bad Execution Plan
以 StateProvinceID=9 的 Good Execution Plan 為例:
- Logical Reads: 218,但卻有可能因故誤用,而耗用到 9,141,差距 41.93 倍。
- CPU Time(sec): 0.004337,但卻有可能因故誤用,而耗用到 0.018172,差距 4.19 倍。
以 StateProvinceID=32 的 Good Execution Plan 為例:
- Logical Reads: 6,但卻有可能因故誤用,而耗用到 218,差距 36.33 倍。
- CPU Time(sec): 0.000448,但卻有可能因故誤用,而耗用到 0.002387,差距 5.32 倍。
Lab 1: Parameter Sniffing: Index Seek
Reuse Good
|
Reuse Bad
|
|
1st |
@StateProvinceID = 32
|
|
2nd |
@StateProvinceID= 41
|
|
3rd |
@StateProvinceID= 9
|
Lab 2: Parameter Sniffing: Index Scan
Reuse Good
|
Reuse Bad
|
|
1st |
@StateProvinceID = 9
|
|
2nd |
@StateProvinceID= 32
|
Parameter Sniffing: Pros and Cons
示範環境:SQL Server 2017
01: 建立 Stored Procedure: dbo.up_Parameter_Sniffing
- Input Parameter 是 @StateProvinceID 參數值。
- WHERE 條件式 使用 @StateProvinceID 參數值:由前端程式傳入,等到 compile (編譯) 或 recompile (重新編譯) 期間 ,將其傳遞給 Query Optimizer,藉此機會執行優化,產生有效率的 Execution Plan.
02. 啟用 Enable both of the options: STATISTICS Disk IO, CPU
-- figure 02_Enable both of the options Disk IO, CPU
已知 Good Execution Plan
- StateProvinceID=32, return: 1 row
- Index Seek: [IX_Address_StateProvinceID]
- last_logical_reads: 6
- StateProvinceID=9, return: 4564 rows
- Index Scan: [IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode]
- Suggestion: Missing index
- last_logical_reads: 218
- StateProvinceID=41, return: 1 row
- Index Seek: [IX_Address_StateProvinceID]
- last_logical_reads: 7
- StateProvinceID= 9 回傳資料筆數多達 4564 列,採用 Index Scan。
Lab1: Parameter Sniffing: Index Seek
First time use @StateProvinceID = 32
01. 第一次執行 dbo.up_Parameter_Sniffing,使用 @StateProvinceID = 32
- 系統 compile 此 Execution Plan,使用 @StateProvinceID= 32。
02. 觀察 Execution Plan
- Parameterization 參數化: WHERE StateProvinceID= @StateProvinceID
03. 觀察 效能 STATISTICS 統計資訊: CPU, Disk IO
-- figure 05_Get last performance statistics for cached query
-- figure 06_Look_At_last performance statistics
Summary
@StateProvinceID = 32
- Good Execution Plan
- return: 1 row, Index Seek: [IX_Address_StateProvinceID]
- Table 'Address'. Scan count 1, logical reads 4, physical reads 2,
- LastElapsedTime(sec): 0.070084000
- LastCPUTime(sec)]: 0.000448000
- last_logical_reads: 6
Changing Parameter Values @StateProvinceID = 41
01. 執行 dbo.up_Parameter_Sniffing,使用 @StateProvinceID = 41
02. 觀察 Execution Plan
- Parameterization 參數化: WHERE StateProvinceID= @StateProvinceID
- @StateProvinceID = 32 與 41 ,都是使用 同一份 Execution Plan,減少 compile 所耗費的系統資源。
-- figure 11_StateProvinceID_41_Execution_Plan
03. 觀察 效能 STATISTICS 統計資訊: CPU, Disk IO
-- figure 12_Look_At_last performance statistics
Summary
@StateProvinceID= 41
- Good Execution Plan
- return: 1 row, Index Seek: [IX_Address_StateProvinceID]
- Table 'Address'. Scan count 1, logical reads 5, physical reads 2,
- LastElapsedTime(sec): 0.088746000
- LastCPUTime(sec): 0.000666000
- Last_logical_reads: 7
Look at Execution Plan
01. 觀察 @StateProvinceID= 32 的 Execution Plan XML
-- figure 21_Show_Execution_Plan_XML
02. 觀察 ParameterList element
- ParameterCompiledValue="(32)"
- ParameterRuntimeValue="(32)"
03. 由 SSMS 的 GUI 介面,觀察 Execution Plan: ParameterList
- Parameter Compiled Value: (32)
- Parameter Runtime Value: (32)
- Estimated Subtree Cost: 0.0065704
04. 觀察 ParameterList element
- ParameterCompiledValue="(32)"
- ParameterRuntimeValue="(41)"
05. 由 SSMS 的 GUI 介面,觀察 Execution Plan: ParameterList
- Parameter Compiled Value: (32)
- Parameter Runtime Value: (41)
- Estimated Subtree Cost: 0.0065704
確認
- Reuse 重複使用 同一份 Execution Plan,減少 compile 所耗費的系統資源。
Advantage of Parameter Sniffing
- Reuse Execution Plan, save overhead of recompiling the SQL statement.
- 重複使用,省下 recompile 重新編譯 SQL 陳述式的系統負擔
Side effect for Parameter Sniffing
Changing Parameter Values @StateProvinceID = 9
已知 Good Execution Plan
- StateProvinceID=32, return: 1 row
- Index Seek: [IX_Address_StateProvinceID]
- last_logical_reads: 6
- StateProvinceID=9, return: 4564 rows
- Index Scan: [IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode]
- Suggestion: Missing index
- last_logical_reads: 218
01. 改用 @StateProvinceID = 9 參數值
- 依照先前 Stored Procedure: up_Parameter_Sniffing 範例,繼續沿用 @StateProvinceID = 32 的 Execution Plan,採用 Index Seek: [IX_Address_StateProvinceID]。
- 而當 StateProvinceID = 9 時,Query Optimizer 會是選擇使用 Index Scan: [IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode],並產生 Missing index 的建議。
但因為 Parameter Sniffing 功能:Reuse 同一份 Execution Plan,節省 recompile 重新編譯 SQL 陳述式的系統負擔。
使得 @StateProvinceID = 9,被強迫 reuse 不合適的 Index Seek: [IX_Address_StateProvinceID],導致效能不佳。
Bad Execution Plan
- @StateProvinceID= 9, return: 4564 rows
- Index Seek: [IX_Address_StateProvinceID]
- Unexpected: without missing index
-- figure 31_StateProvinceID_Bad Execution Plan
02. 觀察 ParameterList element
- ParameterCompiledValue="(32)"
- ParameterRuntimeValue="(9)"
-- figure 32_Show Execution Plan XML_9
03. 由 SSMS 的 GUI 介面,觀察 Execution Plan: ParameterList
- Parameter Compiled Value: (32)
- Parameter Runtime Value: (9)
- Estimated Subtree Cost: 0.0065704
請注意
- Estimated Subtree Cost 仍是 0.0065704,這是 @StateProvinceID = 32 評估 的 Subtree Cost 。不是 @StateProvinceID = 9 的 Subtree Cost。
-- figure 33_Look_At_Parameter_List_9
04. 由 SSMS 的 GUI 介面,觀察 Execution Plan:
- Estimated Number of Rows: 1
- Estimated Number of Rows to Read: 1
- Number of Rows Read: 4564
- Actual Number of Rows: 4564
使用了 不適合 的 Execution Plan ,沿用先前的評估值是 1 row,但實際卻是 4564 rows。
-- figure 34_Actual_Estimate_row
05. 點選 Operator 之間的連接線:
- Actual Number of Rows: 4564
- Number of Rows Read: 4564
- Estimated Number of Rows: 1
-- figure 35_Actual_Estimate_row
06. 改用回 StateProvinceID=41,並與 @StateProvinceID=32 比較起來:
- return: 1 row
- Index Seek: [IX_Address_StateProvinceID]
這兩者(32 與 41),適合 reuse 同一份 Execution Plan,節省 recompile 的系統負擔。
- Estimated Number of Rows: 1
- Estimated Number of Rows to Read: 1
- Number of Rows Read: 1
- Actual Number of Rows: 1
-- figure 36_Compare_Actual_Estimate
07. 點選 Operator 之間的連接線:
- Actual Number of Rows: 1
- Number of Rows Read: 1
- Estimated Number of Rows: 1
-- figure 37_Compare_Actual_Estimate
08. 觀察 效能 STATISTICS 統計資訊: CPU, Disk IO
-- figure 38_Look_At_last performance statistics
Summary
@StateProvinceID= 9
- Bad Execution Plan
- return: 4564 row, Index Seek: [IX_Address_StateProvinceID]
- Unexpected: without missing index
- Table 'Address'. Scan count 1, logical reads 9139, physical reads 2,
- LastElapsedTime(sec): 0.170785000
- LastCPUTime(sec): 0.018172000
- Last_logical_reads: 9141
- Maybe Reuse bad Execution Plan.
- 因故 Reuse 到 不合適 的 Execution Plan,使得 效能 更糟,導致耗損 更多 的系統資源。
- 因為 Side effect for Parameter Sniffing,也使得 Estimated Subtree Cost 不具備使用價值。
Remove Cache
-- figure 41_Remove_Cache
Lab2: Parameter Sniffing: Index Scan
First time use @StateProvinceID = 9
01. 第一次執行 dbo.up_Parameter_Sniffing,使用 @StateProvinceID = 9
- 系統 compile 此 Execution Plan,使用 @StateProvinceID= 9。
Good Execution Plan
- @StateProvinceID= 9: return: 4564 rows
- Index Scan:
- [IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode]
- Suggestion: Missing index
-- figure 42_StateProvinceID_9_Good_Execution_Plan
02. 觀察 效能 STATISTICS 統計資訊: CPU, Disk IO
-- figure 43_Look_At_last performance statistics
Summary
@StateProvinceID = 9
- Good Execution Plan
- return: 4564 rows
- Index Scan:
- [IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode]
- Suggestion: Missing index
- Table 'Address'. Scan count 1, logical reads 216, physical reads 1, read-ahead reads 211,
- LastElapsedTime(sec): 0.121807000
- LastCPUTime(sec): 0.004337000
- last_logical_reads: 218
03. 由 SSMS 的 GUI 介面,觀察 Execution Plan: ParameterList
- Parameter Compiled Value: (9)
- Parameter Runtime Value: (9)
- Estimated Subtree Cost: 0.180413
-- figure 44_Actual_Estimate_row
04. 由 SSMS 的 GUI 介面,觀察 Execution Plan: Missing Index
- Suggestion: Missing index
-- figure 45_Execution Plan_Missing_Index
05. 觀察 ParameterList element
- ParameterCompiledValue="(9)"
- ParameterRuntimeValue="(9)"
-- figure 46_Show Execution Plan XML_9
06. 觀察 MissingIndexes element
-- figure 47_XML_Execution Plan_Missing_Index
Changing Parameter Values @StateProvinceID = 32
01. 執行 dbo.up_Parameter_Sniffing,使用 @StateProvinceID = 32
02. 觀察 Execution Plan
- Parameterization 參數化: WHERE StateProvinceID= @StateProvinceID
- @StateProvinceID = 9 與 32 ,都是使用 同一份 Execution Plan,減少 compile 所耗費的系統資源。
- 但卻使用了 不適合 的 Execution Plan
-- figure 51_StateProvinceID_Bad Execution Plan
03. 觀察 ParameterList element
- ParameterCompiledValue="(9)"
- ParameterRuntimeValue="(32)"
-- figure 52_Show Execution Plan XML_32
04. 由 SSMS 的 GUI 介面,觀察 Execution Plan:
- Estimated Number of Rows: 19614
- Estimated Number of Rows to Read: 1
- Number of Rows Read: 4564
- Actual Number of Rows: 19614
使用了 不適合 的 Execution Plan:
- 實際上,因為 Index Scan,存取了 19614 rows
- 實際上,僅需回傳 1 row
- 原先預估,回傳的是 4564 rows
- 原先預估,使用 Index Scan,存取了 19614 rows
-- figure 53_Compare_Actual_Estimate
05. 點選 Operator 之間的連接線:
- Actual Number of Rows: 1
- Number of Rows Read: 19614
- Estimated Number of Rows: 4564
-- figure 54_Compare_Actual_Estimate
06. 由 SSMS 的 GUI 介面,觀察 Execution Plan: ParameterList
- Parameter Compiled Value: (9)
- Parameter Runtime Value: (3)
- Estimated Subtree Cost: 0.180413
請注意
- Estimated Subtree Cost 仍是 0.180413,這是 @StateProvinceID = 9 評估 的 Subtree Cost 。不是 @StateProvinceID = 32 的 Subtree Cost。
-- figure 55_Actual_Estimate_row
07. 觀察 效能 STATISTICS 統計資訊: CPU, Disk IO
-- figure 56_Look_At_last performance statistics
Summary
@StateProvinceID= 32
- Bad Execution Plan
- return: 1 rows
- Index Scan
- [IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCode]
- Suggestion: Missing index
- Table 'Address'. Scan count 1, logical reads 216, physical reads 0
- LastElapsedTime(sec): 0.002388000
- LastCPUTime(sec): 0.002387000
- last_logical_reads: 218
- Maybe Reuse bad Execution Plan.
- 因故 Reuse 到 不合適 的 Execution Plan,使得 效能 更糟,導致耗損 更多 的系統資源。
- 因為 Side effect for Parameter Sniffing,也使得 Estimated Subtree Cost 不具備使用價值。
Sample Code
20171230_Parameter_Sniffing
https://drive.google.com/drive/folders/0B9PQZW3M2F40OTk3YjI2NTEtNjUxZS00MjNjLWFkYWItMzg4ZDhhMGIwMTZl?usp=sharing
參考資料
[SQL Server] Get SubTreeCost, CompileTime, CompileMemory, Execution Count, Plan Recompile from Plan Cache - 子樹成本
http://sharedderrick.blogspot.tw/2017/12/sql-server-get-subtreecost-compiletime.html
[SQL Server] sys.dm_exec_query_stats: Get performance statistics from cached plan, 觀察 Plan Cache 中的效能統計資訊
http://sharedderrick.blogspot.tw/2017/12/sql-server-sysdmexecquerystats-get.html
[SQL Server] Look at parameter from execution plan, 查看 執行計畫 所使用的參數
http://sharedderrick.blogspot.tw/2017/12/sql-server-look-at-parameter-from.html
[SQL Server] Look at Execution Plan and index: use Index Hint, WITH(FORCESEEK), Missing Index
http://sharedderrick.blogspot.tw/2017/12/sql-server-look-at-execution-plan-and.html
Improving query performance with OPTION (RECOMPILE), Constant Folding and avoiding Parameter Sniffing issues
https://blogs.msdn.microsoft.com/robinlester/2016/08/10/improving-query-performance-with-option-recompile-constant-folding-and-avoiding-parameter-sniffing-issues/
How OPTIMIZE FOR UNKNOWN Works
http://www.benjaminnevarez.com/2010/06/how-optimize-for-unknown-works/
Optimize for… Mediocre?
https://www.brentozar.com/archive/2013/06/optimize-for-unknown-sql-server-parameter-sniffing/
Statistics on Computed Columns
Query Optimizer will use the 30% selectivity guess on inequality comparisons.
http://www.benjaminnevarez.com/2011/06/statistics-on-computed-columns/
Stored procedures, recompilation and .NetRocks
https://www.sqlskills.com/blogs/kimberly/stored-procedures-recompilation-and-netrocks/
Query Processing Architecture Guide
https://docs.microsoft.com/EN-US/sql/relational-databases/query-processing-architecture-guide#ParamSniffing
Cardinality Estimation (SQL Server)
https://docs.microsoft.com/EN-US/sql/relational-databases/performance/cardinality-estimation-sql-server
ALTER DATABASE SCOPED CONFIGURATION (Transact-SQL)
https://docs.microsoft.com/EN-US/sql/t-sql/statements/alter-database-scoped-configuration-transact-sql
Adaptive Cursors and SQL Plan Management
http://www.oracle.com/technetwork/articles/sql/11g-sqlplanmanagement-101938.html
Improve SQL Query Performance by Using Bind Variables
https://blogs.oracle.com/sql/improve-sql-query-performance-by-using-bind-variables