Sql-Server

錯誤的實際行數,為什麼統計資訊更新在這裡有幫助?

  • February 13, 2017

在過去的幾天裡,以下簡單查詢多次出現超時:

SELECT  Object1.*,
                       Object2.Column1,
                       Object2.Column2 AS Column3                    
               FROM    Object2 INNER JOIN Object1 ON Object2.Column2 = Object1.Column3
               WHERE   Object2.Column4=Variable1 AND 
                       Object2.Column5=Variable2 AND
                       Object1.Column6=Variable3

我可以在 SentryOne 中擷取它並看到它被頻繁執行,執行到應用程序設置的 60 秒查詢超時並不斷導致 12 次 Mio 讀取。

我看不到任何相關的東西,比如阻塞或死鎖導致它超時。

我複制了查詢並在 SSMS 中執行它。它在幾毫秒內返回並返回零行。這是我得到的執行計劃:https ://www.brentozar.com/pastetheplan/?id=SJ-LK8jug

後來,我使用相同的查詢和相同的參數值再次執行了此步驟。突然,它執行了大約 90 秒,返回零行,我得到了一個不同的計劃,如下所示:https ://www.brentozar.com/pastetheplan/?id=HyVu58i_l

如您所見,估計行數為 1,實際行數很大。這讓我猜到桌子做了很多改動。所以我看著

$$ sys $$.$$ dm_db_stats_properties $$相關表的值,尤其是 OBJECT1 和使用的索引。 $$ Please note to avoid confusion, the anonymized plans use the same names for different indexes (Object1.Index1) $$

在這一點上,我看到了以下統計值……

Object1.Index1(引用第二個效率低下的執行計劃):

RowsInTable=3826101
RowsSampled=103245
UnfilteredRows=3826101
RowMods=2140 
Histogram Steps 200
PercentChanged=0.0

對於 Object1.Index2(聚集索引):

RowsInTable=3826101
RowsSampled=103734
UnfilteredRows=3826101
RowMods=2140
HistoSteps=199
PercentChanged=0.0

然後我意識到我在第一次執行時不小心添加了換行符,我認為這讓我有了一個不同的新執行計劃。

我決定更新 OBJECT1 表的所有統計資訊。之後,我再次執行初始查詢,因為我從 SentryOne 擷取它而沒有任何更改,沒有換行符……

這一次它像預期的那樣快,執行計劃與第一個高效計劃相同。這讓我懷疑統計數據有點陳舊。

我再次查詢統計元資訊,結果如下(參考第一個有效的計劃)

Object1.Index1(聚集索引)

RowsinTable=3828157
RowsSampled=104017
UnfilteredRows=3828157
RowModifications=14
HistoSteps=199
PercentCahnge=0.0

對於 Object1.Index2(非聚集索引)

RowsInTable=3828157
RowsSampled=103275
UnfilteredRows=3828157
RowMods=14 
HistogrSteps=127
PercentChanged=0.0

之後應用程序按預期執行,速度很快,沒有超時。所以我猜 STATISTICS UPDATE 在這裡有所幫助。

讓我另外指出,作為我的自動化夜間索引和統計維護的一部分,表的所有索引都已在昨晚成功維護/更新。

現在我的問題:

  • 我知道,如果執行計劃期望的行數很少並且實際上返回的行數比預期的多,那麼執行計劃就會出現問題。如果執行計劃實際上返回零行,我不明白執行計劃如何顯示 3,141,000 行。這怎麼可能?
  • 對錶 OBJECT 1 及其統計數據的調查沒有顯示任何較大更改或添加行的提示。自上次自動索引和統計資訊維護以來,我查詢了添加或更改的行,看起來有 2370 行已更改,而表中約有 3,800,000 行。這是一個輕微的變化,因為值

$$ sys $$.$$ dm_db_stats_properties $$也顯示了。統計數據真的是一個問題嗎?我上面引用的數字是否顯示了統計更新的任何充分理由?

更新: ParameterCompiledValue 和 ParameterRuntimeValue 的值在 GOOD PLAN 中相同,但在 BAD PLAN 中不同。表 OBJECT1 在 Column6 中有一個值提供 > 3 Mio 行,而所有其他值最多提供大約 60k 行。BAD 計劃對 ParameterRuntimeValue 使用了大於 3 Mio Rows 的值,而編譯時使用的值只能提供 160 行。所以看起來我需要一個解決這兩種情況的計劃,或者一個更靈活的解決方案來創建一個適當的計劃……?

關於您的第一個問題:

如果執行計劃實際上返回零行,我不明白執行計劃如何顯示 3,141,000 行。這怎麼可能?

優化器在生成計劃時不知道最終的輸出行數。所以它所能考慮的只是它可以從統計中計算出來的估計值。(在您的“壞”計劃的情況下,估計的輸出行實際上是 4.4,這是基於計劃中的第一個估計。)

如果這些估計已經過時,或者不夠準確(例如,不均勻分佈數據的樣本與全掃描),那麼即使使用簡單的查詢也可能會生成糟糕的計劃。

此外,如果計劃與不同的變數一起重複使用,則生成該計劃的估計可能非常不准確。(我認為這就是 sp_BlitzErik 在您的特定情況下傾向於作為原因的原因。)


更新:

您的最新更新表明您看到的問題是由經典的不適當參數嗅探引起的。

最簡單的解決方案(如果您控制程式碼)是將 OPTION (RECOPILE) 添加到有問題的查詢的末尾。這將確保在每次執行時重新創建語句計劃,並且還允許在創建計劃時採用某些快捷方式。

缺點是額外的 CPU 和每次執行創建計劃所花費的時間,因此該解決方案可能不適合。

考慮到數據的偏差(一個值 3 磨,其他值最大 160k),並假設偏差不會發生太大變化,這樣的分支可能會奏效:

IF @Variable3 = 3MillValue

           SELECT  Object1.*,
                       Object2.Column1,
                       Object2.Column2 AS Column3                    
               FROM    Object2 INNER JOIN Object1 ON Object2.Column2 = Object1.Column3
               WHERE   Object2.Column4=@Variable1 AND 
                       Object2.Column5=@Variable2 AND
                       Object1.Column6=@Variable3
               OPTION (OPTIMIZE FOR (@Variable3=3MillValue));
  ELSE
           SELECT  Object1.*,
                       Object2.Column1,
                       Object2.Column2 AS Column3                    
               FROM    Object2 INNER JOIN Object1 ON Object2.Column2 = Object1.Column3
               WHERE   Object2.Column4=@Variable1 AND 
                       Object2.Column5=@Variable2 AND
                       Object1.Column6=@Variable3

請注意,兩個地方的“3MillValue”需要使用返回該數量的值進行硬編碼,而 OPTIMIZE FOR 是該技術的關鍵。

引用自:https://dba.stackexchange.com/questions/163899