本文出處:http://www.cnblogs.com/wy123/p/6770258.html html
統計信息寫過幾篇了相關的文章了,感受仍是不過癮,關於統計信息的問題,最近又踩坑了,該問題雖然不算很常見,但也比較有意思。
相對SQL Server 2012,發如今新的SQL Server版本(2014,2016)中都有一些明顯的變化,下文將對此進行粗淺的分析。算法
SQL Server 2012中(包括以前的版本),因表中數據變化,但統計信息還沒有更新的狀況下,對於直方圖中沒有覆蓋到的謂詞過濾時,sqlserver老是預估爲1行
SQL Server 2014和 Server 2016中這種估算方式都有所變化,從表現看,對於對於沒有覆蓋到的謂詞過濾的預估,每一個版本都是不一樣的。
本文簡單測試一下此種狀況在SQL Server 2012,SQL Server 2014,SQL Server 2016的不一樣表現,以及該問題可能形成的潛在影響。sql
下面涉及到的測試環境的數據庫版本以下數據庫
測試環境準備緩存
首先利用以下腳本,建一張測試表,寫入測試數據,下面會解釋測試數據的分佈ide
create table A ( IdentifierId int identity(1,1), Id1 int, Id2 int, OtherCol CHAR(500) ) GO begin tran declare @i int = 1 while @i<=1000000 begin insert into A values ((@i/50000)+1,@i,NEWID()) set @i = @i+1 if (@i%500000)=0 begin if @@TRANCOUNT>0 begin commit begin tran end end end if @@TRANCOUNT>0 begin commit end GO
插入的測試數據的分佈以下,Id1是從1~20,每個Id1對應50000個不一樣的Id2oop
統計信息直方圖中覆蓋到的謂詞的預估sqlserver
測試:根據直方圖中的任何一個Id來作查詢,查詢以前先建立相關列上的統計信息,發現預估行數是絕對準確的。性能
查看idx_1上的統計信息,上面預估的絕對準確就歸結於統計信息100%的取樣統計以及Rang_Hi_key的EQ_Rows,直方圖中的Id1的分佈是1~21測試
統計信息直方圖中未覆蓋到的謂詞的預估
繼續插入一個與上面Id2都不同的數據,這裏爲50,由於此時插入的是50000行數據,同時又不足以觸發統計信息更新,所以發生以下寫入數據以後,統計信息並不會更新。
所以這個插入完成以後,統計信息並無更新。
由於統計信息沒有更新,在idx_1的直方圖中,是沒有Id1=50的信息的,也就說Id1=50不存在於統計信息的直方圖中,
在SQL Server 2012中預估的結果:預估爲1行,實際爲50000行
重複以上測試代碼,分別在SQL Server 2014和SQL Server 2016中測試,不重複截圖了
SQL Server 2014中測試以下:行預估爲1024.7,實際爲50000,
這個值是經過什麼方式計算出來的?暫時還沒查到資料。
能夠肯定的是,對於相似狀況的預估算法,也就是謂詞沒有包含在統計信息直方圖中的狀況下(one specifies a value which is out of range of the current statistics)
在sqlserver 2014中,經測試,不一樣狀況下預估是不同的,不是固定的預估爲1行,也不是固定預估爲的0.1%,也不是簡單的Rows Sampled*All density
SQL Server 2016中測試以下: 預估爲49880.8,實際爲50000,基本上接近於真實值。
相對於SQL Server 2012和2014的預估結果,這個預估的準確性看起來仍是比較吊的。
爲何SQL Server 2016中預估的如此準確?
由於在SQL Server 2016中,對於直方圖中不存在的過濾謂詞,在用這個謂詞進行查詢的時候,會自動更新相關的統計信息,而後再執行查詢,
這個特性,相對於SQL Server 2012和2014來講,是全新的,也是很是實用的。
SQL Server 2014這個預估策略雖然在2012的基礎上作出了一些改進,可是仍是沒有解決本質問題,以致於人仍舊要人爲地干預統計信息的更新。
在SQL Server 2016中,即使是當前表中改變的數據行尚未達到觸統計信息更新閾值的條件(傳統上所謂的閾值,500+rowcount*20%),
統計信息依然會在查詢的驅動下更新,經過索引上的統計信息能夠看到,參考下圖,直方圖中生成了一個50的統計。
下面就是所謂觸發統計信息更新閾值的條件(嚴格說是該規則僅對SQL Server 2016以前的版本有效,不適應於SQL Server 2016)
1,表格從沒有數據變成有大於等於1條數據。
2,對於數據量小於500行的表格,當統計信息的第一個字段數據累計變化量大於500之後。
3,對於數據量大於500行的表格,當統計信息的第一個字段數據累計變化量大於500 + (20%×表格數據總量)之後。
這個說法,對於SQL Server 2016以前的版本是有效的,對於SQL Server 2016以後的版本是不成立的,我想這個仍是值得注意的。
SQL Server 2016中統計信息更新策略至關於以前版本中開啓了TraceFlag 2371,參考http://www.cnblogs.com/wy123/p/5748933.html
也即決定統計信息的變化值爲動態的,再也不拘泥於「數據累計變化量大於500 + (20%×表格數據總量)」這一限制。
除此以外,應該還要其餘機制,好比這裏的查詢所觸發的。
形成的問題
爲何微軟會在SQL Server 2016中將統計信息的更新策略作出如此的改變,以及爲何筆者會來探究這個問題?
固然在實際業務中被這個問題坑的蛋疼。
問題很明顯,相似於測試的場景,在SQL Server 2012(包括以前的版本),這種預估策略存在的嚴重的缺陷。
好比示例中:
由於沒有當前過濾謂詞的統計信息(或者說沒有收集到當前謂詞的統計信息),實際爲5000行的狀況下,預估爲1行。
這種預估策略很是離譜,某種狀況下會形成嚴重的性能問題,估計也很容易猜到,只是遇到的比較少罷了.
下面就簡單具體說明,會形成什麼問題,以及緣由。
上述問題在什麼狀況下會形成性能問題,以及影響又多嚴重,這裏僅簡單舉例說明。下面這個測試是在SQL Server 2012下進行的。
爲演示這個問題,先來作另一張測試表B,並寫入測試數據。
create table B ( IdentifierId int identity(1,1), Id2 int, OtherCol char(500) ) GO begin tran declare @i int = 1 while @i<=1000000 begin insert into B values (@i,NEWID()) set @i = @i+1 if (@i%100000)=0 begin if @@TRANCOUNT>0 begin commit begin tran end end end if @@TRANCOUNT>0 begin commit end GO
create index idx_2 on B(Id2)
GO
藉助第二張表作一個測試,從而把錯誤預估行數形成的缺陷給放大,
執行下面兩個SQL,分別查詢A.Id1 = 5和A.Id1 = 50的信息,
由數據分佈可知,查詢總的結果總數會徹底同樣(截圖受影響行數),
雖然A.Id1 = 5和A.Id1 = 50的數據量和分佈也徹底同樣,可是後者的邏輯IO遠遠超出前者。
就是由於直方圖中沒有A.Id1 = 50的統計信息,A.Id1 = 50被錯誤地預估爲1行形成的。
具體緣由就很明瞭的,瞭解執行計劃的同窗應該很清楚。
由於錯誤地預估了當前謂詞過濾的行數,在A表上,採用索引查找的方式來查詢數據,
事實證實,當前狀況下,這是比全表掃描更加低效的一種方式(看邏輯IO),這是其一。
另外A表查詢以後驅動B表的過程當中,由於預估爲一行,採用了Nested Loop的方式來驅動B表作鏈接,
事實上當前狀況下Nested Loop並不是最好的,能夠說是很很差的。
這裏也能夠歸結爲統計信息的直方圖中沒有過濾謂詞上的統計信息,在第一個階段的預估中錯誤地估算爲1行形成的。
這種問題更蛋疼的地方在於,檢查Session或者緩存的執行計劃的時候,會發現,表面上看,執行計劃挺好的啊,都用到索引了。
好比第二個SQL的執行計劃,看起來彷佛沒問題,也容易直接忽略這個形成的問題,
從而把重點轉向其餘地方,使得問題變得更加難以甄別。其實問題正是出在錯誤地使用了索引,不應使用索引的地方使用了索引。
這就是執行計劃第一步選擇錯誤,形成後面每一步都錯誤的狀況(一步錯,步步錯),實際狀況中,SQL更加複雜,數據量也更大,形成的影響也更大。
若是上述示例中在再多幾張表join,會出現清一色的Nested Loop方式來驅動錶鏈接,這樣的話,SQL執行時間和邏輯IO是很是高的。
附上一個在SQL Server 2016下的測試截圖,可見在默認狀況下,執行計劃作出了正確的選擇。
最後:
1,本文不是說索引的,關於索引的就很少說。
2,本文也的場景雖然不是太常見,稍顯特殊,但也是實際遇到的,另外能夠看出,微軟也在從這個方面逐步改進SQL Server優化器更新統計信息的策略。
3,關於此場景下的預估,在不一樣版本下,還有很多有意思問題沒有拋出來,有機會再說。
4,相似問題只有在數據量相對較大的狀況下才能發生,若是是十萬如下或者幾十萬的數據量,對數據庫來講算是微小型數據量,相似問題對性能的影響徹底體現不出來。
5,若是有人根據本文的測試驗證的話,請注意一個細節:對於過濾謂詞的預估,分以下兩種狀況,這兩種狀況在2012和2014(2016)中預估的方式也是不一樣的
1,表中確實沒有這個謂詞的數據,而且統計信息沒有更新,好比Id1 = 50的數據爲0行的狀況下的預估
2,表中有這個謂詞的數據,一樣是統計信息沒有更新,好比Id1 = 50的數據爲50000行的狀況下的預估
總結:
SQL Server 的預估對執行計劃的生成有着相當重要的影響,而預估又依賴於統計信息,所以統計信息的更新以及準確性就顯得尤其重要。鑑於此,SQL Server在每一個版本中,對於統計信息的生成以及更新策略都有着比較大的變化,本文僅僅從一個較小的點出發,來驗證SQL Server各個版本中統計信息預估以及更新的一些特色,從中發現相似問題可能產生的潛在的影響,以及SQL Server 2016中的一些改進。