前提
本文僅討論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