咱們知道數據庫中的統計信息的準確性是很是重要的。它會影響執行計劃。一直想寫一篇關於統計信息影響執行計劃的相關博客,可是都卡在如何構造一個合適的例子上,因此一直拖着沒有寫。巧合,最近在生產環境中遇到這麼一個案例,下面對案例中的相關信息作了脫敏處理,有些中間步驟也省略了,只關注核心部分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只有一個彙集索引,因此執行計劃中,這個表走彙集索引掃描。數據庫
注意:這裏表WDPM.PdaRecords自己缺乏合適的索引,只有一個彙集索引。後面展開講述這個問題.這裏先圍繞統計信息的準確性對執行計劃的影響來展開講述。app
物理表WDPM.PdaRecords的數據量爲2505369(固然這個是一直在變化的。這個數值僅僅是實驗前的檢測記錄,一直有會話對其進行DML操做,因此數據會變化,因此這裏沒有列出統計信息截圖)。oop
咱們看到Table Scan部分,預估行數(Estimated Number of Rows)爲1, 實際行數爲150。 這個誤差已經比較大了。優化
對於物理表WDPM.PdaRecords而言,基數估計的預估行數(Estimated Number of Rows)爲921771, 可是因爲嵌套循環鏈接,因此累加起來的實際行數(Actual Number of Rows)爲: 921771*150 =138265650 。spa
咱們知道嵌套循環(Nested Loops)算法的時間複雜度爲N*M, N的預估值從1變成了150 ,這裏面的誤差就大了(由於每次彙集索引掃描的開銷也很大)。因此致使優化器在表的物理鏈接方式上選擇了嵌套循環(Nested Loops), 由於預估的代價是很小的。可是實際由於統計信息的偏差,致使這個代價放大了150倍。那麼若是咱們更新臨時表的統計信息呢?而後執行這個SQL,會有什麼變化呢? code
以下所示,咱們在執行SQL語句前,更新一下臨時表的統計信息。發現優化器在獲取了準確的統計信息後,在表的物理鏈接上選擇了Hash Join方式。並且SQL語句耗時變成了1秒多。爲何呢? 由於優化器發現選擇Nested Loops的代價遠遠高於 Hash Join。因此它在獲取了準確的信息後,做出了最優選擇。以前之因此生成了一個錯誤的執行計劃,就是由於它獲得的「信息」不許確,致使它做出了錯誤的抉擇。這個就比如你獲取了錯誤的信息,做出了錯誤的選擇,購買了一隻錯誤的股票,而巴菲特因爲掌握了準確的行業信息,做出了正確的選擇。 購買了幾隻購票都大漲了。orm
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];
固然,瞭解到這裏,還遠遠沒有結束。咱們發現表WDPM.PdaRecords 只有一個彙集索引,並且彙集索引位於Iden自增字段上,從另一個角度來看,這個表實際上是缺乏合適的索引的。那麼咱們能夠建立一個索引。blog
CREATE INDEX IX_PdaRecords_N1 ON wdpm.PdaRecords(OrderNo,FunctionName)索引
建立索引後,即便不更新臨時表#Batch的統計信息,咱們發現執行計劃也會走嵌套循環(Nested Loops),而不會走Hash Join了。這個又是什麼緣由呢?
此處截圖,是第二次執行SQL,臨時表的數據變化了(生成臨時表的數據的SQL有好幾個,每次執行獲取的數據都會有部分變化)
由於有了合適的索引,趨近準確的統計信息,以及謂詞下推(predicate push down),基數(Cardinality)的預估行數(Esitmted Row Size)爲35.0545 與實際行數(Actual Number of Rows)爲666, 這樣即便循環次數爲140. 總的訪問記錄數爲140*666=93240 , 這個是遠遠小於以前錯誤執行計劃的138265650 。因此即便臨時表的#Batch的統計信息有誤,可是優化器仍是生成了一個不錯的執行計劃。這樣SQL的執行時間也就縮短到了1秒內.
這個案例僅僅是爲了展現:統計信息的準確與否,會致使優化器生成的執行計劃選擇不一樣的錶鏈接方式, 例如從嵌套循環(Nested Loops)變成Hash Join。 僅僅是爲了說明統計信息準確的重要性。