2018-01-20

[SQL Server] Ad hoc Query: Local Variable and Without Local Variable




延續前文:[SQL Server] Parameter Sniffing: Local Variable


來個有趣的問題,給 SQL Server Developer:
以下這兩段 SQL Query,有何不同?

What's the difference between 01 and 02?


-- figure 01_Whats the difference between 01 and 02










大相徑庭 的 Execution Plan

  • 即便回傳筆數相同
  • 但 02. 使用 Local Variable


-- figure 02_Execution_Plan


換成是 Ad hoc Query,也受到 Local Variables / OPTIMIZE FOR UNKNOWN 的影響,停用 Parameter Sniffing 。


評估方式
"Local Variables / OPTIMIZE FOR UNKNOWN" 

ConditionsQuery Optimizer only use
equality: =Rows * All density
inequality: >  ,  < , etc., Rows * 30%



當條件式是:
  • 使用 equality (等號),則 Query Optimizer 僅使用 density vector 來執行 Cardinality Estimation,也就是: Rows * All density
  • 使用 Inequality (不相等),則 Query Optimizer 連 density vector 也不用了,就直接設定資料列總數的 30% 來執行 Cardinality Estimation,也就是: Rows * 30% 。


Compare Match and Not match Execution Plan


以 Local Variables / OPTIMIZE FOR UNKNOWN 為例:


若  ProductID  = 897


  • Not match Execution Plan, Bad
  • 理想是 Logical Reads: 14,若改用 Local Variable,耗用增加到 1246,差距 89 倍。
  • 原本是 CPU Time(sec): 0.000488000,若改用 Local Variable,耗用增加到 0.011253000,差距 23 倍。





Ad hoc Query: Local Variable and Without Local Variable


01. 檢視:資料表 Sales.SalesOrderDetail 與 Index 資訊。


  • 資料表總筆數是:121,317 rows


-- figure 01_Get Table and Index Information




02. 檢視 依據 ProductID 分組資料


  • ProductID = 897,  return 2 rows.
  • ProductID = 870,  return 4688 rows.


-- figure 02_Query Data by ProductID




03. 執行查詢 WHERE ProductID = 897,檢視 Execution Plan



WHERE ProductID = 897
  • Good Execution Plan


  • return 2 row
  • Index Seek: [IX_SalesOrderDetail_ProductID]


  • Number of Rows Read: 2
  • Actual Number of Rows: 2
  • Estimated  Number of Rows: 75.6667
  • Estimated Number of Rows to be Read: 75.6667


-- figure 11_ProductID_897_Execution_Plan




Actual Number of Rows: 2
Number of Rows Read: 2
Estimated  Number of Rows: 75.6667


-- figure 12_Estimated  Number of Rows



04. 觀察 效能 STATISTICS  統計資訊: CPU, Disk IO


  • LastElapsedTime(sec): 0.073422000
  • LastCPUTime(sec)]: 0.000488000
  • LastRows: 2
  • EstimatedNumberofRows: 75.6667
  • Last_logical_reads: 14


-- figure 13_Performance_Statistics






Ad hoc Query with Local Variable


01. 宣告 DECLARE @ProductID INT = 897:


  • WHERE ProductID = @ProductID
  • Use Local Parameter


觀察 Execution Plan:

@ProductID INT = 897

  • Not match Execution Plan, Bad


  • return 2 row
  • Clustered Index Scan: [PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID]
  • Suggestion: Missing index ???
  • Number of Rows Read: 121317
  • Actual Number of Rows: 2
  • Estimated  Number of Rows: 456.079
  • Estimated Number of Rows to be Read: 121317


-- figure 21_Ad hoc Query with Local Variable





  • Actual Number of Rows: 2
  • Number of Rows Read: 121317
  • Estimated  Number of Rows: 456.079


-- figure 22_Estimated  Number of Rows





02. 觀察 效能 STATISTICS  統計資訊: CPU, Disk IO


  • LastElapsedTime(sec): 0.028126000
  • LastCPUTime(sec)]: 0.011253000
  • LastRows: 2
  • EstimatedNumberofRows: 456.079
  • Last_logical_reads: 1246


-- figure 23_Performance_Statistics







Why does Estimated  Number of Rows get 456.079 ?


為什麼是使用 EstimatedNumberofRows: 456.079 來評估建立 Execution Plan 呢?


01. Show Execution Plan XML,可以到相關的資訊。


-- figure 31_Show Execution Plan XML



02. 使用 DBCC SHOW_STATISTICS,顯示目前的 query optimization 的 statistics。


  • Query Optimizer 使用 statistics 來預估 Cardinality (基數) 或 查詢結果中的資料列數,如此可讓 Query Optimizer 建立高品質的 Execution Plan。
  • 舉例來說,Query Optimizer 可使用 Cardinality Estimates (CE) 估來選擇 Execution Plan 中的 index seek operator,而不是 index scan operator,避免發生 resource-intensive index scan 來提高查詢效能。


Query Optimizer 使用 statistics 物件中的相關資料來計算 Cardinality Estimates (CE) 。

DBCC SHOW_STATISTICS 可以顯示儲存在 statistics 物件中的資料,包含:


  • Header (標頭)
  • Density Vector (密度向量)
  • Histogram (長條圖)

請參考 [SQL Server] Parameter Sniffing: OPTION OPTIMIZE FOR UNKNOWN 2
http://sharedderrick.blogspot.tw/2018/01/sql-server-parameter-sniffing-option_14.html



觀察 DBCC SHOW_STATISTICS 所顯示的資料。


  • 在 Header(標頭)
    • Rows: 121317
  • 在 Density Vector (密度向量)
    • All density: 0.0.003759399


-- figure 41_Displays Query Optimization statistics





03. 謎底揭曉:


  • 0.003759399 * 121317 = 456.079008483,約等於 456.079


-- figure 51_All density






當 Ad hoc Query with Local Variable and Equality Operator :


  • 使用 equality (等號),則 Query Optimizer 僅使用 density vector 來執行 cardinality estimation,也就是: Rows * All density




Ad hoc Query with Local Variable and Inequality Operator


Ad hoc Query and Inequality Operator

Use Inequality Operators, 使用 不相等

01. 執行查詢 WHERE ProductID > 897,檢視 Execution Plan

WHERE ProductID > 9999

  • Good Execution Plan


  • return 0 row
  • Index Seek: [IX_SalesOrderDetail_ProductID]
  • Number of Rows Read: 0
  • Actual Number of Rows: 0
  • Estimated  Number of Rows: 1
  • Estimated Number of Rows to be Read: 1


-- figure 61_Ad hoc Query and Inequality Operator





  • Actual Number of Rows: 0
  • Number of Rows Read: 1


-- figure 62_Estimated  Number of Rows



03. 觀察 效能 STATISTICS  統計資訊: CPU, Disk IO


  • LastElapsedTime(sec): 0.022990000
  • LastCPUTime(sec)]: 0.000421000
  • LastRows: 0
  • EstimatedNumberofRows: 1
  • Last_logical_reads: 2


-- figure 63_Performance_Statistics







Ad hoc Query with Local Variable and Inequality Operator


01. 宣告 DECLARE @ProductID INT > 9999:


  • WHERE ProductID > @ProductID
  • Use Local Parameter
  • Use Inequality Operator


觀察 Execution Plan:

WHERE ProductID > @ProductID

  • Not match Execution Plan, Bad
  • return 0 row
  • Clustered Index Scan: [PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID]
  • Number of Rows Read: 121317
  • Actual Number of Rows: 0
  • Estimated  Number of Rows: 36395.1
  • Estimated Number of Rows to be Read: 121317


-- figure 71_Ad hoc Query with Local Variable and Inequality Operator





  • Actual Number of Rows: 0
  • Number of Rows Read: 121317
  • Estimated  Number of Rows: 36395.1


-- figure 72_Estimated  Number of Rows





02. 觀察 效能 STATISTICS  統計資訊: CPU, Disk IO


  • LastElapsedTime(sec): 0.031641000
  • LastCPUTime(sec)]: 0.009111000
  • LastRows: 0
  • EstimatedNumberofRows: 36395.1
  • Last_logical_reads: 1246


-- figure 73_Performance_Statistics




為什麼是使用 EstimatedNumberofRows: 36395.1 來評估建立 Execution Plan 呢?


03. 觀察 DBCC SHOW_STATISTICS 所顯示的資料


  • 在 Header(標頭)
  • Rows: 121317


-- figure 81_Displays Query Optimization statistics




-- 04. 謎底就是:


  • Inequality: EstimatedNumberofRows is 30% of the rows
  • 121317 * 0.3 = 36395.1


-- figure 82_EstimatedNumberofRows is 30




當 Ad hoc Query with Local Variable and Inequality Operator


  • Query Optimizer 一律都 僅 使用 Rows * 30% 方式來評估


也就是說,輸入什麼 parameter,回傳多少筆數,Query Optimizer 都不列入考量。




Sample Code

20180120_Ad hoc Query_Local Variable
https://drive.google.com/drive/folders/0B9PQZW3M2F40OTk3YjI2NTEtNjUxZS00MjNjLWFkYWItMzg4ZDhhMGIwMTZl?usp=sharing




參考資料

[SQL Server] Parameter Sniffing: Local Variable
http://sharedderrick.blogspot.tw/2018/01/sql-server-parameter-sniffing-local.html

沒有留言:

張貼留言