天天看點

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

統計資訊寫過幾篇了相關的文章了,感覺還是不過瘾,關于統計資訊的問題,最近又踩坑了,該問題雖然不算很常見,但也比較有意思。

相對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 Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)
SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)
SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

測試環境準備

首先利用如下腳本,建一張測試表,寫入測試資料,下面會解釋測試資料的分布

插入的測試資料的分布如下,Id1是從1~20,每一個Id1對應50000個不同的Id2

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  

統計資訊直方圖中覆寫到的謂詞的預估

  測試:根據直方圖中的任何一個Id來做查詢,查詢之前先建立相關列上的統計資訊,發現預估行數是絕對準确的。

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  檢視idx_1上的統計資訊,上面預估的絕對準确就歸結于統計資訊100%的取樣統計以及Rang_Hi_key的EQ_Rows,直方圖中的Id1的分布是1~21

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

統計資訊直方圖中未覆寫到的謂詞的預估

  繼續插入一個與上面Id2都不一樣的資料,這裡為50,因為此時插入的是50000行資料,同時又不足以觸發統計資訊更新,是以發生如下寫入資料之後,統計資訊并不會更新。

  是以這個插入完成之後,統計資訊并沒有更新。

    

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  因為統計資訊沒有更新,在idx_1的直方圖中,是沒有Id1=50的資訊的,也就說Id1=50不存在于統計資訊的直方圖中,

  在SQL Server 2012中預估的結果:預估為1行,實際為50000行

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  重複以上測試代碼,分别在SQL Server 2014和SQL Server 2016中測試,不重複截圖了

  SQL Server 2014中測試如下:行預估為1024.7,實際為50000,

  這個值是通過什麼方式計算出來的?暫時還沒查到資料。

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  可以确定的是,對于類似情況的預估算法,也就是謂詞沒有包含在統計資訊直方圖中的情況下(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 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  為什麼SQL Server 2016中預估的如此準确?

  因為在SQL Server 2016中,對于直方圖中不存在的過濾謂詞,在用這個謂詞進行查詢的時候,會自動更新相關的統計資訊,然後再執行查詢,

  這個特性,相對于SQL Server 2012和2014來說,是全新的,也是非常實用的。

  SQL Server 2014這個預估政策雖然在2012的基礎上做出了一些改進,但是還是沒有解決本質問題,以至于人仍舊要人為地幹預統計資訊的更新。

  在SQL Server 2016中,即便是目前表中改變的資料行還沒有達到觸統計資訊更新門檻值的條件(傳統上所謂的門檻值,500+rowcount*20%),

  統計資訊依然會在查詢的驅動下更新,通過索引上的統計資訊可以看到,參考下圖,直方圖中生成了一個50的統計。

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  下面就是所謂觸發統計資訊更新門檻值的條件(嚴格說是該規則僅對SQL Server 2016之前的版本有效,不适應于SQL Server 2016)

    1,表格從沒有資料變成有大于等于1條資料。

    2,對于資料量小于500行的表格,當統計資訊的第一個字段資料累計變化量大于500以後。

    3,對于資料量大于500行的表格,當統計資訊的第一個字段資料累計變化量大于500 + (20%×表格資料總量)以後。

  這個說法,對于SQL Server 2016之前的版本是有效的,對于SQL Server 2016之後的版本是不成立的,我想這個還是值得注意的。

也即決定統計資訊的變化值為動态的,不再拘泥于“資料累計變化量大于500 + (20%×表格資料總量)”這一限制。

除此之外,應該還要其他機制,比如這裡的查詢所觸發的。

造成的問題

為什麼微軟會在SQL Server 2016中将統計資訊的更新政策做出如此的改變,以及為什麼筆者會來探究這個問題?

當然在實際業務中被這個問題坑的蛋疼。

問題很明顯,類似于測試的場景,在SQL Server 2012(包括之前的版本),這種預估政策存在的嚴重的缺陷。

比如示例中:

因為沒有目前過濾謂詞的統計資訊(或者說沒有收集到目前謂詞的統計資訊),實際為5000行的情況下,預估為1行。

這種預估政策非常離譜,某種情況下會造成嚴重的性能問題,估計也很容易猜到,隻是遇到的比較少罷了.

下面就簡單具體說明,會造成什麼問題,以及原因。

  上述問題在什麼情況下會造成性能問題,以及影響又多嚴重,這裡僅簡單舉例說明。下面這個測試是在SQL Server 2012下進行的。

  為示範這個問題,先來做另外一張測試表B,并寫入測試資料。

借助第二張表做一個測試,進而把錯誤預估行數造成的缺陷給放大,

執行下面兩個SQL,分别查詢A.Id1 = 5和A.Id1 = 50的資訊,

由資料分布可知,查詢總的結果總數會完全一樣(截圖受影響行數),

雖然A.Id1 = 5和A.Id1 = 50的資料量和分布也完全一樣,但是後者的邏輯IO遠遠超出前者。

就是因為直方圖中沒有A.Id1 = 50的統計資訊,A.Id1 = 50被錯誤地預估為1行造成的。

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  具體原因就很明了的,了解執行計劃的同學應該很清楚。

  因為錯誤地預估了目前謂詞過濾的行數,在A表上,采用索引查找的方式來查詢資料,

  事實證明,目前情況下,這是比全表掃描更加低效的一種方式(看邏輯IO),這是其一。

  另外A表查詢之後驅動B表的過程中,因為預估為一行,采用了Nested Loop的方式來驅動B表做連接配接,

  事實上目前情況下Nested Loop并非最好的,可以說是很不好的。

  這裡也可以歸結為統計資訊的直方圖中沒有過濾謂詞上的統計資訊,在第一個階段的預估中錯誤地估算為1行造成的。

 

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

  這種問題更蛋疼的地方在于,檢查Session或者緩存的執行計劃的時候,會發現,表面上看,執行計劃挺好的啊,都用到索引了。

  比如第二個SQL的執行計劃,看起來似乎沒問題,也容易直接忽略這個造成的問題,

  進而把重點轉向其他地方,使得問題變得更加難以甄别。其實問題正是出在錯誤地使用了索引,不該使用索引的地方使用了索引。

  這就是執行計劃第一步選擇錯誤,造成後面每一步都錯誤的情況(一步錯,步步錯),實際情況中,SQL更加複雜,資料量也更大,造成的影響也更大。

  如果上述示例中在再多幾張表join,會出現清一色的Nested Loop方式來驅動表連接配接,這樣的話,SQL執行時間和邏輯IO是非常高的。

  附上一個在SQL Server 2016下的測試截圖,可見在預設情況下,執行計劃做出了正确的選擇。

  

SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)
SQL Server 中統計資訊直方圖中對于沒有覆寫到謂詞預估以及預估政策的變化(SQL2012-->SQL2014-->SQL2016)

 最後: 

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中的一些改進。