SQL Server索引進階第十四篇:索引統計

  在第十篇文章中我們詳述了爲什麼索引需要葉子節點和非葉子節點,我原文是”然而,SQL Server並不知道什麼是按字母表排序”,換句話說,SQL Server並不知道“Meyer, Helen”這個條目大概在索引的中間位置。

    但是SQL Server需要知道有關索引中數據分佈的情況;正如我們所知,查詢的選擇性是決定是否使用特定索引的關鍵。SQL Server存儲索引鍵中的部分值,這部分值就是所謂的索引統計信息,也可以直接叫做統計信息,統計信息是本篇文章要涵蓋的主題。
    實際上,統計信息不僅僅是索引列值的採樣,統計信息也會採樣非索引列,這部分信息的結構和採樣索引列的結構是一致的,但由於本系列文章是關於索引的,所以這裏僅對統計列進行討論。
    索引統計有點像汽車中的引擎。當然你知道引擎的原理那是再好不過的,但如果你不懂引擎的原理,那依然需要定期維護引擎。本篇文章講述索引統計的原理和維護這兩方面。

索引統計的結構
    索引統計信息分爲三部分:索引頭信息,密度信息和數據分佈信息,如果想查看這三部分信息,則運行:
  1. DBCC SHOW_STATISTICS (tablename, indexname)
複製代碼
而如果要分別查看這三個部分中某一個部分的信息,則運行:
  1. DBCC SHOW_STATISTICS (tablename, indexname) WITH STAT_HEADER
複製代碼
或是:
  1. DBCC SHOW_STATISTICS (tablename, indexname) WITH DENSITY_VECTOR
複製代碼
或是:
  1. DBCC SHOW_STATISTICS (tablename, indexname) WITH HISTOGRAM
複製代碼
在我們開始查看統計信息之前,首先我們來建立一個表和索引來作爲示例進行演示。我們的示例表由3列和兩個索引組成。兩個索引分別是表的前兩列和第三列。代碼1所示。
  1. USE AdventureWorks; 

  2. GO



  3. IF EXISTS (SELECT * 

  4. FROM sys.objects 

  5. WHERE name = 'HistogramTest' AND type = 'U')

  6. BEGIN

  7. DROP TABLE dbo.HistogramTest

  8. END

  9. GO

  10. CREATE TABLE dbo.HistogramTest

  11. (

  12. Col1 int not null

  13. , Col2 int not null

  14. , Col3 int not null

  15. )

  16. GO



  17. CREATE INDEX IX_HistogramTest

  18. ON dbo.HistogramTest

  19. ( Col1, Col2 )

  20. CREATE INDEX IX_SingleValue

  21. ON dbo.HistogramTest

  22. ( Col3 )

  23. GO 
複製代碼
代碼1.有兩個索引的示例表

    表建好之後,讓我們來插入一些測試數據,如代碼2所示。
  1. SET NOCOUNT ON;

  2. GO

  3. DECLARE @maxLeftColumnValue int = 100;

  4. DECLARE @leftColumnValue int = 0;

  5. DECLARE @middleColumnValue int = 0;

  6. DECLARE @rightColumnValue int = 0;

  7. WHILE @leftColumnValue < @maxLeftColumnValue

  8. BEGIN

  9. SET @leftColumnValue += 1

  10. SET @middleColumnValue = 0

  11. WHILE @middleColumnValue < @leftColumnValue 

  12. BEGIN

  13. SET @middleColumnValue += 1

  14. SET @rightColumnValue += 1

  15. INSERT dbo.HistogramTest VALUES ( @leftColumnValue

  16. , @middleColumnValue

  17. , @rightColumnValue )

  18. END 

  19. END

  20. GO 
複製代碼
代碼2.插入一些測試數據

    測試數據特點如下:



  • Col1中的數據相同值的行數和其值相同,比如值爲1的有1行,值爲2的有2行,依此類推
  • 但對於行來說,Col1和Col2作爲組合列值是唯一的。所以建立在其上的IX_HistogramTest索引可以看所是唯一索引
  • Col3中的值是唯一的,從1開始不斷遞增,因此IX_SingleValue也可以看作是唯一索引
    爲了演示的準確性,上面的順序是先創建表,再插入測試數據,然後再創建索引,這樣就能保證索引的統計信息是最新的。
    完成上面的步驟之後,運行代碼3中所示的查詢。可以看到結果和我們上面所說的數據特點一致。
    因爲這部分數據是按照Col1/Col2排序的,因此可以看所是IX_Histogram索引的部分數據。
  1. SELECT TOP 20 Col1, Col2, Col3

  2. FROM dbo.HistogramTest 

  3. ORDER BY Col1, Col2; 
複製代碼
代碼3.查詢語句

圖1.查詢結果

密度
    另一個有關索引的術語就是密度。密度是衡量索引中數據重複程度的一個術語,和我們之前文章所謂的選擇性關係緊密。比如說密度是0.01代表着索引某列中有1/0.01=100個不相同的值。換句話說,平均每一個值出現的概率是1%。密度可以衡量單一列的密度,也可以用於衡量組合列的密度,比如前面例子中的Col1-Col2組合列。
    如果圖1的查詢結果是整個表,則Col1的密度會是1/6=0.1667,而col3的密度是1/20=0.05
    密度越小,則查詢中比較謂詞所能匹配的數據越少。則選擇性越高。比如Col3的選擇性就比Col1高。

統計信息頭
執行下面語句:
  1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
  2. WITH STAT_HEADER
複製代碼
我們可以看到如圖2所示的統計頭信息:

圖2.統計信息頭

頭部信息涵蓋了如下內容:
Name:索引信息名稱。
Updated:統計信息的更新時間。
Rows:索引中包含的條目數。
Steps:步長,代表了數據分佈信息的份數。
Density:這個值在SQL Server 2008之後被淘汰。
Average Key Length:平均鍵的長度.
String Index:用於Like匹配時估計行數
Filter Expression:過濾索引表達式
Unfiltered Rows:索引所在表的條目數,如果是過濾索引,這個值通常更大。

統計信息密度
執行下面語句:
  1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest') 
  2. WITH DENSITY_VECTOR 
複製代碼
我們可以看到如圖3所示的結果:

圖3.密度信息

通過圖3的密度信息可以看出:
Col1平均的鍵大小是4字節,包含1/0.01=100個不同的值。
Col1-Col2組合鍵值的平均大小是8字節,包含1/0.0001980198=5050個不同的值,因爲這兩列組合起來是唯一的,所以這個5050等於表中的行數。

統計數據分佈直方圖
執行下面代碼後:
  1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
  2. WITH HISTOGRAM
複製代碼
我們可以看到如圖4所示的信息,我們截取59個步長中的25個,顯示在下面:

圖4.統計分佈直方圖的前25個步長信息

數據分佈直方圖更像是一個5列的表。每一行所存儲的信息都可以稱爲步長。所有的步長信息都取自索引鍵最左邊列的信息。對於我們之前創建的IX_HistogramTest索引,只有COL1的值被採樣,其它列在生成數據分佈信息的過程中直接被無視。
每一個步長都是統計分佈的一個範圍,換句話說是一部分連續索引條目的集合。無論表有多大,數據分佈直方圖不會超過200個步長。每個步長都包含不同大小的索引條目,比如說當前步長包含30個索引條目,下一個步長包含了46個索引條目。
爲了更好的理解數據分佈直方圖,我們定義如下4個術語,並在圖5中進行詳細闡述。



  • 最索引的最左列是索引鍵中出現在第一個位置的列,比如我們的例子中,Col1就是左列
  • 步長集就是索引中連續條目的集合.圖4中這5列值都是根據索引鍵中的最左列決定的
  • 步長中的Range_High_key是在步長中最左列的最大值
  • 步長中的更小子集是那些小於步長中最左列最大值的集合
比如說,我們通過圖4中部分統計分佈圖來看上面的概念,如圖5所示。


圖5.索引中的步長集

    因爲上面的例子最左列只有100個不同的值,所以每一個步長僅僅包含2個值或是更少,一會在本文的後續部分,我們用一個更大的例子來展示包含多個值的步長。
圖4中的5列的統計分佈直方圖中包含的值計算方法如下:

RANGE_HI_KEY:
    步長集合中的最左列的最大值。RANGE_HI_KEY是步長的邊界,換句話說,不同的步長不和能有相同的RANGE_HI_KEY。
    對於第一個步長來說,RANGE_HI_KEY是最左列最小值,因此第一個步長沒有低子集.對於最後一個步長來說,RANGE_HI_KEY是索引最左列的最大值。
    步長按照RANGE_HI_KEY順序維護和顯示。
    我們的例子中,第一個RANGE_HI_KEY的值是1,也就是Col1的最小值。最後一個RANGE_HI_KEY的值是100,也就是Col1的最大值。59個步長中沒有一個相同的RANGE_HI_KEY值。
    因爲RANGE_HI_KEY是區分步長的一句,因此這個值也可以唯一確定步長,因此步長的RANGE_HI_KEY=20的話,無論這個步長是該索引的第幾個步長,我們都可以其爲步長20。

EQ_ROWS:

    最左列值等於RANGE_HI_KEY的行數,也就是步長的大子集(Upper subset)的個數。
    在我們的例子中,步長20中有20個等於該步長RANGE_HI_KEY值的條目。這個值和測試數據生成規則推算出來的值一樣。

RANGE_ROWS:
    步長小子集(lower subset)中條目的個數
    我們的例子中,步長20代表着最左列的值從18到20(18<步長<=20),其中值爲19的就是小子集(lower subset),值等於19的條目數有19個。

DISTINCT_RANGE_ROWS:
    步長小子集(lower subset)中唯一值的個數。
    我們的例子中,步長20代表着最左列的值從18到20(18<步長<=20),其中值爲19的就是小子集(lower subset),這個值只有1個。

AVG_RANGE_ROWS
     最左值的最小集合中每個唯一值條目個數的平均數,可以使用公式RANGE_ROWS / DISTINCT_RANGE_ROWS進行計算,如果RANGE_ROWS等於0,那麼這個值就是1。因爲如果RANGE_ROWS等於0的話,這列值毫無意義。

一個大一點的例子
    前面討論的例子數據量比較小。這也使得我們可以更簡單的知道上面所說的各類值的意義。但是對於SQL Server來說,大表比小表更需要統計信息來做正確的查詢計劃選擇。因此,我們將上面的數據量擴大100倍。
    簡而言之,我們將生成測試數據的變量值進行修改。
由:
  1. DECLARE @maxLeftColumnValue int = 100;
複製代碼
變爲:
  1. DECLARE @maxLeftColumnValue int = 1000;
複製代碼
我們重新創建表,插入測試數據,並重建了索引。這時表中的數據就編程了505000。
再次運行:
  1. SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest')
複製代碼
可以看到如圖6鎖示的結果,共有199個步長。

圖6.大量數據例子的索引統計

圖6中的第二個步長,最左列的值爲46,意味着大子集(Upper subset)包含46行(每行的最左列的值都是46),這點可以根據我們前面數據生成算法推出。在小子集(Lower subset)包含1034個條目(2+3+4+44+45),這些數據共有44個不同值(從2到45)。則平均每一個不同最左列的值有23.5行。
所以,當SQL Server解析下面這句時:
WHERE Col1 = 46
SQL Server就會知道500500行中有46行滿足這個where條件。
而如果謂語是:
WHERE Col1 = 45
因爲這裏行不在RANGE_HI_KEY中,所以SQL Server就採用平均值,也就是23.5行。
即使遇到如下謂語:
WHERE Col1 BETWEEN 2 AND 24
SQL Server也能知道將會有517行(大概上面的小子集(Lower subset)中一半的行)。

唯一索引和統計信息
    我們前面所創建的兩個索引IX_HistogramTest和IX_SingleValue都符合建立唯一索引的條件,在實際生產環境中它們也應該是被設置成唯一索引。雖然SQL Server可以從統計信息中得知這兩個索引中鍵值是唯一的,但是這是無法保證的。所以僅僅靠索引統計信息是無法保證SQL Server在生成執行計劃的過程中確定索引中鍵值的唯一性。在本系列的第八篇關於唯一索引的文章中,我們已經知道SQL Server是如何利用唯一索引來生成更好的執行計劃的,但僅靠索引統計信息不行。
    同樣,如果只存在唯一索引而沒有統計信息,SQL Server則僅僅知道值是唯一的而不知道值到底是什麼。因此爲了知道索引中值的範圍,SQL Server首先要查看統計信息,比如我們之前創建的IX_SingleValues索引,SQL Server首先要知道下面的信息:
  1. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_SingleValue')
複製代碼
信息的結果如圖7所示。

圖7.SQL Server對於單列唯一索引的統計信息

SQL Server需要在查詢過程中得知如下兩部分信息:



  • 索引包含500500個條目
  • 這些條目由500500(1+500548+1)個不同值組成,從1到500500
得知了這些信息之後,遇到WHERE子句中不管是=,<,>或是between,SQL Server都可以根據上面的信息快速估計返回的行數。
字符串統計信息
    字符串類型(包括Char,varchar,nchar,nvarchar和ntext類型)和數字類型不同之處在於字符串類型是可以分割的。比如substring函數或是like操作需要SQL Server去匹配字符串的子集。因此,SQL Server需要一些統計信息來得知特定字符串子集的值出現的概率。這就是所謂的字符串統計信息。這個統計信息是在SQL Server 2008之後纔有的。
    如果統計頭中StringIndex列的值是Yes,則SQL Server爲最左列的值生成字符串統計信息,如果最左列的字符串統計信息超過80字節,則只有開始40字節和結束40字節被用來生成統計信息。
    執行DBCC SHOW_STATISTICS是無法找到關於字符串統計的細節的,SQL SERVER也沒有其它方式可以展現字符串統計信息。

索引統計信息的維護
    統計信息的用處是巨大的。但如果統計信息過時了,也就是反映不出表中的數據分佈了則就沒那麼擁有了。統計信息過時的原因是SQL Server並不會每次在表中插入和刪除信息時就同時更新統計信息。如果更確切的說,SQL Server不會更新統計信息,而是重新生成統計信息。但我們也不希望SQL Server重新生成統計信息的次數超過必須的次數。
    因此我們需要知道什麼時候需要重新生成統計信息。

何時更新統計信息
    所幸,SQL Server允許你控制何時重新生成統計信息。
    數據庫有一個選項叫做AUTO_UPDATE_STATISTIC,這個選項默認是ON。可以通過ALTER DATABASE語句進行設置。當這個選項設置爲ON的時候,表中一定量的數據改變就會引起SQL Server重新生成執行計劃,這個“一定量”是由SQL Server控制的,也就是不能通過選項進行控制。
    每當創建或修改索引時,都可以通過STATISTICS_NORECOMPUTE選項來控制索引的統計信息是否會被自動更新。
    如果你想手動更新統計信息,使用下面語句:
  1. UPDATE STATISTICS SchemaName.TableName
複製代碼
上面的語句會更新與表相關的所有統計信息,而如果你指向更新特定索引的話,請使用:
  1. UPDATE STATISTICS SchemaName.TableName IndexName
複製代碼
除了這種方法之外,sp_updatestats存儲過程也可以用來更新統計信息。

通常來說,AUTO_UPDATE_STATISTICS保持默認是不錯的選擇。不僅可以省去麻煩,還能讓SQL Server來管理索引統計信息的生成。
但有些時候你還是想手動更新統計信息,比如下面情況:



  • 你們公司有定期的維護窗口時間,這個時間內你想手動更新統計信息
  • 存在一個很大的表,自動更新統計信息需要達到的閾值太大
  • 索引中含有多個唯一鍵值,這需要很大的樣本數據才能生成有意義的統計信息。默認的樣本百分比打不到這個標準
  • 統計信息滯後與表中數據的改變
爲了演示上面最後一種情況,並且更好的理解爲什麼有些時候不用自動更新統計信息。請看代碼4中創建的表,這個表中存儲借出圖書的信息。
  1. CREATE TABLE dbo.Loan
  2. (
  3. MemberNo int not null
  4. , ISBN SysName not null
  5. , DateOut Date not null
  6. , DateDue Date not null
  7. )
  8. GO
複製代碼
代碼4.Load表

    上面4列分別代表:



  • 借書人的會員ID
  • ISBN號
  • 借出日期
  • 還書日期

    因爲所有借出去的書需要在14天內還回來,所以14天內數據是不會被刪除的。這裏書借出去就插入一條記錄,書被換回來則把借書記錄刪除。在兩週內,通常ISBN號的分佈幾乎不會有太大變化。但還書日期值的分佈卻有很大變化。
    ISBN號的變化不大是因爲很少有哪本書這周不流行,但下週突然流行。因此,ISBN號的數據分佈本月和上月一般沒有什麼太大變化。單個ISBN號肯定有變化,但總體的數據樣本不會有太大變化。
    但還書日期列可就不同了,因爲這個日期的範圍是當前日期+14天,所以數據分佈隨時會變,並且這列的值不會和過去的值重疊。因此DueDate列的索引需要比ISBN列的索引更新的更加頻繁。

最小化更新統計信息帶來的影響
    SQL Server還可以允許用戶控制更新統計信息時讀取索引的數量或百分比。



  • 當索引創建完成後,必須讀取所有的索引條目,因此統計信息需要讀取所有的索引條目才能生成樣本
  • 當更新索引時,如果還需要採樣索引的所有條目就有點小題大做了。這會消耗大量的IO,一般這時SQL SERVER只會採樣索引的一部分。
  • 默認情況下SQL Server控制採樣的大小,但是你也可以設置這個大小。當更新索引時,你可以指定SAMPLE N PERCENT或是SAMPLE n ROWS亦或是FULLSCAN選項。當你指定了SAMPLE N PERCENT或是SAMPPLE N ROWS,這個效果會立刻在接下來的更新操作和以後的自動更新操作中生效。
    爲了闡述不是默認全表採樣的部分採樣過程,我們重新創建了前面的那個500500行的大表,但這次,我們在創建完之後,分別執行:
  1. UPDATE STATISTICS dbo.HistogramTest;
  2. DBCC SHOW_STATISTICS('dbo.HistogramTest', 'IX_HistogramTest');
複製代碼
我們看到如圖8所示的結果。

圖8.採樣的統計信息

    雖然圖6和圖8中的信息貌似相同,但細節上還是不同的。RANGE_ROWS中的實際實際行數被FLOAT類型的估計行數所替代。

總結
    索引統計幫助SQL Sever優化查詢。
    索引創建和重建時統計信息會自動生成。
    索引統計信息分爲三部分,可以通過DBCC指定進行查看。
    索引統計可以過時,過時的統計信息會導致不精確的查詢計劃。
    默認情況下,索引統計信息會自動更新,你也可以不允許自動更新,也可以手動更新統計信息。
    通常來說更新統計信息並不需要讀取索引的所有葉子節點,而是採樣部分索引。你可以通過參數來控制採樣數據的百分比。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章