SQL Server統計信息偏差影響表聯結方式案例淺析


 

我們知道數據庫中的統計信息的准確性是非常重要的。它會影響執行計划。一直想寫一篇關於統計信息影響執行計划的相關博客,但是都卡在如何構造一個合適的例子上,所以一直拖着沒有寫。巧合,最近在生產環境中遇到這么一個案例,下面對案例中的相關信息做了脫敏處理,有些中間步驟也省略了,只關注核心部分SQL。如下所示,同事反饋一個SQL語句執行很慢。

 

 

 
UPDATE  b
SET     b.[Status] = '已掃描,未簽收' ,
        b.[Time] = pr.CreatedDate
FROM    #Batch b
        JOIN WDPM.PdaRecords pr WITH ( NOLOCK ) ON b.Batch_No = pr.OrderNo
                                                              AND pr.FunctionName = '[WDPM].[usp_SaveOutOrder]'
WHERE   b.[Status] = '已打單,未掃描'
        AND pr.CreatedDate > b.[Time];

 

 

 

如下截圖所示,這個SQL語句基本上耗時271秒。一個臨時表與一個表做嵌套循環連接(Nested Loops)。 因為表WDPM.PdaRecords只有一個聚集索引,所以執行計划中,這個表走聚集索引掃描。

 

 

 

 

clip_image001

 

 

注意:這里表WDPM.PdaRecords本身缺少合適的索引,只有一個聚集索引。后面展開講述這個問題.這里先圍繞統計信息的准確性對執行計划的影響來展開講述。

 

clip_image002

 

 

物理表WDPM.PdaRecords的數據量為2505369(當然這個是一直在變化的。這個數值僅僅是實驗前的檢測記錄,一直有會話對其進行DML操作,所以數據會變化,所以這里沒有列出統計信息截圖)。

 

clip_image003

 

我們看到Table Scan部分,預估行數(Estimated Number of Rows)為1, 實際行數為150。 這個偏差已經比較大了。

 

 

clip_image004

 

對於物理表WDPM.PdaRecords而言,基數估計的預估行數(Estimated Number of Rows)為921771, 但是由於嵌套循環連接,所以累加起來的實際行數(Actual Number of Rows)為: 921771*150  =138265650  。

 

clip_image005

 

clip_image006

 

 

我們知道嵌套循環(Nested Loops)算法的時間復雜度為N*M N的預估值從1變成了150 ,這里面的偏差就大了(因為每次聚集索引掃描的開銷也很大)。所以導致優化器在表的物理連接方式上選擇了嵌套循環(Nested Loops), 因為預估的代價是很小的。但是實際因為統計信息的誤差,導致這個代價放大了150倍。那么如果我們更新臨時表的統計信息呢?然后執行這個SQL,會有什么變化呢? 

 

如下所示,我們在執行SQL語句前,更新一下臨時表的統計信息。發現優化器在獲取了准確的統計信息后,在表的物理連接上選擇了Hash Join方式。而且SQL語句耗時變成了1秒多。為什么呢? 因為優化器發現選擇Nested Loops的代價遠遠高於 Hash Join。所以它在獲取了准確的信息后,作出了最優選擇。之前之所以生成了一個錯誤的執行計划,就是因為它得到的信息不准確,導致它作出了錯誤的抉擇。這個就好比你獲取了錯誤的信息,作出了錯誤的選擇,購買了一只錯誤的股票,而巴菲特由於掌握了准確的行業信息,作出了正確的選擇。 購買了幾只購票都大漲了。

 

 
UPDATE STATISTICS #Batch WITH FULLSCAN;
 
 
UPDATE  b
SET     b.[Status] = '已掃描,未簽收' ,
        b.[Time] = pr.CreatedDate
FROM    #Batch b
        JOIN WDPM.PdaRecords pr WITH ( NOLOCK ) ON b.Batch_No = pr.OrderNo
                                                              AND pr.FunctionName = '[WDPM].[usp_SaveOutOrder]'
WHERE   b.[Status] = '已打單,未掃描'
        AND pr.CreatedDate > b.[Time];

 

 

 

clip_image007

 

 

clip_image008

 

當然,了解到這里,還遠遠沒有結束。我們發現表WDPM.PdaRecords 只有一個聚集索引,而且聚集索引位於Iden自增字段上,從另外一個角度來看,這個表其實是缺少合適的索引的。那么我們可以創建一個索引。

 

CREATE INDEX IX_PdaRecords_N1 ON wdpm.PdaRecords(OrderNo,FunctionName)

 

 

創建索引后,即使不更新臨時表#Batch的統計信息,我們發現執行計划也會走嵌套循環(Nested Loops),而不會走Hash Join了。這個又是什么原因呢?

 

clip_image009

 

此處截圖,是第二次執行SQL,臨時表的數據變化了(生成臨時表的數據的SQL有好幾個,每次執行獲取的數據都會有部分變化)

 

clip_image010

 

因為有了合適的索引,趨近准確的統計信息,以及謂詞下推(predicate push down),基數(Cardinality)的預估行數(Esitmted Row Size)為35.0545 與實際行數(Actual Number of Rows)為666, 這樣即使循環次數為140.  總的訪問記錄數為140*666=93240 , 這個是遠遠小於之前錯誤執行計划的138265650 。所以即使臨時表的#Batch的統計信息有誤,但是優化器還是生成了一個不錯的執行計划。這樣SQL的執行時間也就縮短到了1秒內.

 

 

clip_image011

 

這個案例僅僅是為了展示:統計信息的准確與否,會導致優化器生成的執行計划選擇不同的表連接方式, 例如從嵌套循環(Nested Loops)變成Hash Join。 僅僅是為了說明統計信息准確的重要性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM