前提 本文僅討論SQL Server查詢時, 對於非複合統計信息,也即每個欄位的統計信息只包含當前列的數據分佈的情況下, 在用多個欄位進行組合查詢的時候,如何根據統計信息去預估行數的。 利用不同欄位的統計信息做數據行數預估的演算法原理,以及SQL Server 2012和SQL Server 2014 ...
前提
本文僅討論SQL Server查詢時,
對於非複合統計信息,也即每個欄位的統計信息只包含當前列的數據分佈的情況下,
在用多個欄位進行組合查詢的時候,如何根據統計信息去預估行數的。
利用不同欄位的統計信息做數據行數預估的演算法原理,以及SQL Server 2012和SQL Server 2014該演算法的差異情況,
這裡暫時不涉及複合統計信息,暫不涉及統計信息的更新策略及優化相關話題,以及其他SQL Server版本計算方式。
統計信息是什麼
簡單說就是對某些欄位的數據分佈的一種描述,讓SQL Server在根據條件做查詢的時候,大概知道預期的數據大小,
從而指導生成合理執行計劃的一種資料庫對象
統計信息的分類
索引上會自動創建統計信息,SQL Server也會根據具體的查詢,在某些非索引自動創建索引,當然也可以通過手動方式創建統計信息。
先來直觀地瞭解一下統計信息長什麼樣,參考截圖,就是這麼個樣子,
_WA_Sys_****開頭的是系統根據需要創建的統計信息,
與索引同名的是索引上創建的統計信息,
手動創建統計信息也可以在滿足SQL Server命名要求的情況下自行命名。
下麵一個是索引的統計信息。
統計信息的作用
查詢引擎根據統計信息提供的數據做出合理的執行計劃。
那麼,查詢引擎究竟是怎麼利用統計信息做預估的呢,
以及下麵將要提到的SQL Server 2014中較之前的版本有哪些變化?
本文將對此兩點做一個簡單的分析來說明SQL Server是怎麼根據統計信息做估算的,下麵開始正文。
測試環境搭建
習慣性地做一個演示的環境,創建一個表,寫入100W的數據後面測試用。
create table TestStatistics ( Id int identity(1,1), Status1 int, Status2 int, Status3 int )
insert into TestStatistics values (RAND()*1000,RAND()*250,RAND()*50) go 1000000
表中有四個欄位,第一個是自增列,主要看Status1,Status2,Status3這三個欄位,
三個欄位的取值都是用隨機數乘以一個常量繫數的出來的,
因此這三個欄位的數據分佈範圍分別是
Status1:0-999(1000種數據分佈)
Status2:0-249(250種數據分佈)
Status3:0-49(50種數據分佈)
這個後面有用。
首先在SQL Server 2012中做測試
先做這麼一個查詢:select * from TestStatistics where Status1=885 and Status2=88 and Status3=8
這個查詢完成之後,表上自動創建一個三個統計信息,
這三個統計信息分別是Status1,Status2,Status3這個三個欄位的數據分佈描述
首先來看一下其中這個_WA_Sys_00000002_0EA330E9,也即Status1這個列的統計信息的詳細信息,
註意All density欄位值,選擇性是反應一個表中該欄位的重覆數據有多少或者說唯一性有多少,
計算方法是:1/表中該欄位非重覆個數。
上面說了,這個Status1這個列的取值範圍是0-999,一共有1000中取值可能行,
那麼這個選擇行就是1/1000=0.001,所以也是吻合這裡的All density=0.001的
照這麼計算,其餘兩個欄位的選擇度分別是1/250=0.004 和1/50=0.02,分別如下截圖的 All density。
執行計劃對數據行的預估
說完統計信息的基礎問題之後,我們就可以來觀察執行計劃對目標數據的預估規律了。
我們來看這麼一個查詢,如下,註意這個是查詢的條件是參數變數,而不是直接的值,後面我會解釋為什麼這麼做。
來觀察執行計劃對數據行的預估:可以看出來,預估為4行。
那麼這個4行是怎麼計算出來的呢?
這就要利用到我們上面的選擇性了,
Status1欄位的選擇性是0.001,Status2的選擇性是0.04,
在SQL Server 2012中,對數據行的預估計算方式是各個欄位的選擇性的乘積,
假如Pn代表不同欄位的選擇性,那麼預估行數的計算方法就是: 預估行數=p0*p1*p2*p3……*RowCount
因此,執行計劃顯示的:預估行數=0.001*0.004*總行數(也即1000000)= 4
說到這裡解釋兩個可能存在的幾個疑問:
第一,上述示例是用兩個欄位查詢的,為什麼不拿三個欄位做演示說明?
首選,不管是多少個欄位查詢,預估行數符合上述計算方式是沒有問題的,
但是如果通過上述公式計算出來的結果非常小,在少於1的情況下,SQL Server顯示預估為1行。
按照上述計算方法,用三個欄位做查詢,
預估行數=0.001*0.004*0.02*總行數(也即1000000)= 0.08<1,所以預估為1行。
第二,為什麼不直接用值查詢,而是用變數做查詢?
熟悉SQL Server的同學應該都知道,直接用變數查詢的時候,SQL Server編譯的時候不知道具體的參數值,
在不知道具體參數值的情況下,它是使用欄位的選擇性的時候是用到一般性(或者說是平均)的值,
也就是統計信息中整體計算出來欄位的選擇性,也即All density=0.001
這裡暫定認為數據分佈是均勻的,也即每個值分佈差別不大。
但事實上每個值的分佈的差別還有存在的,
尤其是分佈不均勻的時候,當然這個是另外一個非常大的話題了,這裡暫不討論。
如果直接用明確的值做查詢。
比如 select * from TestStatistic where Status1=885 and Status2=88
SQL Server會根據統計信息中每個欄位 :Status1=885 的行數和 Status2=88行數的具體的值,
利用上述公式做預估
那麼就繼續用具體的值做演示說明,
可以直接用where Status1=885 and Status2=88這個條件查詢來觀察預估結果。
首先我們看統計信息中Status1=885 的分佈行數,1079行
然後再看統計信息中Status2=88 的分佈行數,3996行
利用上述公式,預估行數為4.31168行
那麼直接利用值做查詢是不是這個預估的行數呢?直接上圖,完美地吻合了上述的計算方法得到的結果。
第三,沒有索引的情況下是符合預估的計算方法,如果創建了索引呢?
查詢條件中的各個列的統計信息是非相關的,
如果分別在各個列上創建單個列的索引信息,在查詢的時候也屬於非相關統計信息。
如截圖,也就是說,雖然創建了索引,執行計劃發生了變化,
從一開始的表掃描變成了通過兩個索引查找後做hash join,然後Loop join查詢數據,咱不管它就是變成什麼執行計划了
但是統對數據的預估還是跟上面全表掃描一樣的,都是預估為4.31168,沒有因為創建了索引以及執行計劃發生了變化而改變(預估行數)。
因為即便是創建了單列上的索引,執行計劃變了,但是統計信息還是非相關的,也就是一個統計信息只描述一列欄位的分佈情況。
然後在SQL Server 2014中做測試
上述同樣的數據,我這裡通過link server 將上述SQL Server 2012實例下的測試表的結果導入到SQL Server 2014的實例下的表中。
現在表結構和數據完全一致。
首選,做一個同樣的測試,利用兩個變數查詢的查詢條件做查詢,看看SQL Server 2014預估的演算法有什麼變化。
還記得上面在 SQL Server 2012中同樣的寫法,同樣的數據的預估的情況吧,剛纔預估的是4行,現在怎麼變成63.2456行了?
預估行數的計算公式變了嗎,當然變了,這正是本文要說的重點。
那麼SQL Server 2014中是怎麼預估的呢?公式是這麼來的:預估行數 = P0*P11/2 * P21/4 * P31/8……* RowCount
那麼來根據此計算方式來計算預估行數的問題:預估行數=0.001*0.0041/2*1000000 = ?
這裡我就不做開方運算了,拿來主義,直接用SQL Server來算拉倒了,SQL Server給我們提供了一個開方函數(SQRT),真JB好用。
計算一下結果吧,
沒錯,是63.24555,保留四位有效數字的話就是63.2456了,預估行數跟上面計算出來的結果也是完全吻合的。
補充測試1:
同樣地,用三個條件做查詢,預估演算法也同樣複合上述公式的結果。
按照公式來計算預估行數,選擇性按照整體計算出來的選擇性來,同樣也是吻合的。
補充測試2:
如果把查詢條件換做具體的值,跟在SQL Server 2012中一樣,SQL Server2014 也同樣會根據具體的值得數據做計算
進行這麼個查詢:select * from TestStatistics2014 where Status1=858 and Status2=88
解釋一下為什麼這次Status1換成858了:
因為即便表結構,數據完全一致吧,受限於統計信息的步長(Steps)只有200,兩個庫的統計信息也不完全一致,統計信息不能精確到任何一個值,
我們這裡為了演示這個演算法,找一個具體的RANGE_HI_KEY值,比較容易說明問題。
首先看Status1=858的數據分佈情況
再看Status2=88的數據分佈情況
利用上述計算方法計算出來的預估:63.27713
執行計劃的預估:63.27713,也是完全吻合的。
補充測試3,在查詢列上創建創建單獨的索引
跟SQL Server 2012中一樣,執行計劃發生了變化 ,但是對於數據行的預估,同樣並沒有因為執行計劃的變化而(預估行數)變化。
雖然執行計劃變了,但是對數據的預估並沒有變化,預估的演算法還是符合:預估行數 = P0*P11/2 * P21/4 * P31/8……* RowCount
在此可以看出,執行計劃對於(未超過統計信息範圍的情況下)數據行的預估,是有一定規律的,
這個規律就是:
SQL Server 2012 中,預估行數=p0*p1*p2*p3……*RowCount(Pn為查詢欄位的選擇性),
SQL Server 2014 中,預估行數= P0*P11/2 * P21/4 * P31/8……* RowCount(Pn為查詢欄位的選擇性)。
當然如果說統計信息過期或者取樣密度不夠,那就另當別論了,這個就關係到統計信息的更新策略問題了,也是一個非常大而且非常現實的問題,暫不深入展開討論。
所以一開始我說暫不考慮統計信息自身是否理想,這裡是在統計信息非常完整的情況下做測試的。
微軟為什麼在SQL Server 2014中,對非相關且未超出統計信息範圍的預估行數演算法做這麼一個變化,
因為PN的值是小於1的
預估行數的計算方法從p0*p1*p2*p3……*RowCount變化為P0*P11/2 * P21/4 * P31/8……* RowCount,顯然是增加了預估行數的大小,
同時本文未提及的另外一種情況:對於超出統計信息範圍的情況下,新的預估方法也增加預估行數的大小,
從整體上看,演算法是傾向於"估多不估少”的,有這麼一個改變
至於為什麼要做出這個改變?
如果經常做SQL優化的就會發現,不少問題都是少估了預期的數據行數(因為種種原因吧,這裡暫時不討論為什麼少估),
造成執行SQL時分配的資源不夠,從而拖慢了SQL的執行效率
一個非常典型的問題就是,預估的數據比實際的數據行數小,造成比如記憶體授予的不夠大,以及實際運算過程中採用不合理的執行計劃
個人認為,(控制在一定範圍之內的)估多的情況下可以通過獲取更多的系統資源來提升SQL的執行效率,
正常情況下也不會說是跟實際值差的太離譜造成資源的浪費。
當然也有特殊情況,那就另當別論
要註意的是我這裡有個前提,非相關的統計信息,不管是沒有任何索引,還是是創建和單列上的索引,對應的統計信息,都屬於非相關統計信息,
如果創建複合索引(有人習慣叫組合索引),那麼執行計劃對於數據行的預估並不符合上述演算法,具體演算法我也不清楚。
此種情況下,在SQL Server 2012和SQL Server 2014中預估演算法也不一樣,這個有機會再研究吧。
對於測試結果的補充說明:
測試過程中一定要保證統計信息的完整性,以及取樣的百分比問題,理性情況下都是按照100%取樣的,
中間我略去了一些細節問題,比如沒此測試之前都會 update statistics TestStatistic with fullscan,保證100%取樣。
既然要精確到小數點後幾位,當然要求條件是理想情況下的,目的就是一定要排除其他條件對測試結果的影響。
總結:
本文通過一個簡單的示例,來瞭解了SQL Server通過統計信息對數據預估的計算方式和原理,以及SQL Server 2012和SQL Server2014之間的差異。
統計信息對於SQL執行計劃的選擇起著中樞神經般的作用,不光是在SQL Server資料庫中,包括其他關係資料庫,統計信息都是一個非常重要的資料庫對象。
可以說,SQL優化,統計信息以及與之息息相關的執行計劃是一個非常重要的因素,瞭解統計信息方面的知識對性能調優有著非常重要的作用。
在涉及到組合索引上的統計信息情況下,執行計劃對數據行的預估,SQL Server2012和SQL Server 2014中也不一樣,問題將會更加有趣,待有時間再寫吧。
參考:Fanr_Zh 大神的 http://www.cnblogs.com/Amaranthus/p/3678647.html
以及 http://msdn.microsoft.com/en-us/library/dn673537.aspx