但是SQL Server需要知道有關索引中數據分佈的情況;正如我們所知,查詢的選擇性是決定是否使用特定索引的關鍵。SQL Server存儲索引鍵中的部分值,這部分值就是所謂的索引統計信息,也可以直接叫做統計信息,統計信息是本篇文章要涵蓋的主題。
實際上,統計信息不僅僅是索引列值的採樣,統計信息也會採樣非索引列,這部分信息的結構和採樣索引列的結構是一致的,但由於本系列文章是關於索引的,所以這裏僅對統計列進行討論。
索引統計有點像汽車中的引擎。當然你知道引擎的原理那是再好不過的,但如果你不懂引擎的原理,那依然需要定期維護引擎。本篇文章講述索引統計的原理和維護這兩方面。
索引統計的結構
索引統計信息分爲三部分:索引頭信息,密度信息和數據分佈信息,如果想查看這三部分信息,則運行: 而如果要分別查看這三個部分中某一個部分的信息,則運行: 或是: 或是: 在我們開始查看統計信息之前,首先我們來建立一個表和索引來作爲示例進行演示。我們的示例表由3列和兩個索引組成。兩個索引分別是表的前兩列和第三列。代碼1所示。 代碼1.有兩個索引的示例表
表建好之後,讓我們來插入一些測試數據,如代碼2所示。 代碼2.插入一些測試數據
測試數據特點如下:
-
Col1中的數據相同值的行數和其值相同,比如值爲1的有1行,值爲2的有2行,依此類推
-
但對於行來說,Col1和Col2作爲組合列值是唯一的。所以建立在其上的IX_HistogramTest索引可以看所是唯一索引
-
Col3中的值是唯一的,從1開始不斷遞增,因此IX_SingleValue也可以看作是唯一索引
完成上面的步驟之後,運行代碼3中所示的查詢。可以看到結果和我們上面所說的數據特點一致。
因爲這部分數據是按照Col1/Col2排序的,因此可以看所是IX_Histogram索引的部分數據。 代碼3.查詢語句
圖1.查詢結果
密度
另一個有關索引的術語就是密度。密度是衡量索引中數據重複程度的一個術語,和我們之前文章所謂的選擇性關係緊密。比如說密度是0.01代表着索引某列中有1/0.01=100個不相同的值。換句話說,平均每一個值出現的概率是1%。密度可以衡量單一列的密度,也可以用於衡量組合列的密度,比如前面例子中的Col1-Col2組合列。
如果圖1的查詢結果是整個表,則Col1的密度會是1/6=0.1667,而col3的密度是1/20=0.05
密度越小,則查詢中比較謂詞所能匹配的數據越少。則選擇性越高。比如Col3的選擇性就比Col1高。
統計信息頭
執行下面語句: 我們可以看到如圖2所示的統計頭信息:
圖2.統計信息頭
頭部信息涵蓋了如下內容:
Name:索引信息名稱。
Updated:統計信息的更新時間。
Rows:索引中包含的條目數。
Steps:步長,代表了數據分佈信息的份數。
Density:這個值在SQL Server 2008之後被淘汰。
Average Key Length:平均鍵的長度.
String Index:用於Like匹配時估計行數
Filter Expression:過濾索引表達式
Unfiltered Rows:索引所在表的條目數,如果是過濾索引,這個值通常更大。
統計信息密度
執行下面語句: 我們可以看到如圖3所示的結果:
圖3.密度信息
通過圖3的密度信息可以看出:
Col1平均的鍵大小是4字節,包含1/0.01=100個不同的值。
Col1-Col2組合鍵值的平均大小是8字節,包含1/0.0001980198=5050個不同的值,因爲這兩列組合起來是唯一的,所以這個5050等於表中的行數。
統計數據分佈直方圖
執行下面代碼後: 我們可以看到如圖4所示的信息,我們截取59個步長中的25個,顯示在下面:
圖4.統計分佈直方圖的前25個步長信息
數據分佈直方圖更像是一個5列的表。每一行所存儲的信息都可以稱爲步長。所有的步長信息都取自索引鍵最左邊列的信息。對於我們之前創建的IX_HistogramTest索引,只有COL1的值被採樣,其它列在生成數據分佈信息的過程中直接被無視。
每一個步長都是統計分佈的一個範圍,換句話說是一部分連續索引條目的集合。無論表有多大,數據分佈直方圖不會超過200個步長。每個步長都包含不同大小的索引條目,比如說當前步長包含30個索引條目,下一個步長包含了46個索引條目。
爲了更好的理解數據分佈直方圖,我們定義如下4個術語,並在圖5中進行詳細闡述。
-
最索引的最左列是索引鍵中出現在第一個位置的列,比如我們的例子中,Col1就是左列
-
步長集就是索引中連續條目的集合.圖4中這5列值都是根據索引鍵中的最左列決定的
-
步長中的Range_High_key是在步長中最左列的最大值
-
步長中的更小子集是那些小於步長中最左列最大值的集合
圖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倍。
簡而言之,我們將生成測試數據的變量值進行修改。
由: 變爲: 我們重新創建表,插入測試數據,並重建了索引。這時表中的數據就編程了505000。
再次運行: 可以看到如圖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首先要知道下面的信息: 信息的結果如圖7所示。
圖7.SQL Server對於單列唯一索引的統計信息
SQL Server需要在查詢過程中得知如下兩部分信息:
-
索引包含500500個條目
-
這些條目由500500(1+500548+1)個不同值組成,從1到500500
字符串統計信息
字符串類型(包括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選項來控制索引的統計信息是否會被自動更新。
如果你想手動更新統計信息,使用下面語句:上面的語句會更新與表相關的所有統計信息,而如果你指向更新特定索引的話,請使用: 除了這種方法之外,sp_updatestats存儲過程也可以用來更新統計信息。通常來說,AUTO_UPDATE_STATISTICS保持默認是不錯的選擇。不僅可以省去麻煩,還能讓SQL Server來管理索引統計信息的生成。
但有些時候你還是想手動更新統計信息,比如下面情況:
-
你們公司有定期的維護窗口時間,這個時間內你想手動更新統計信息
-
存在一個很大的表,自動更新統計信息需要達到的閾值太大
-
索引中含有多個唯一鍵值,這需要很大的樣本數據才能生成有意義的統計信息。默認的樣本百分比打不到這個標準
-
統計信息滯後與表中數據的改變
上面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,這個效果會立刻在接下來的更新操作和以後的自動更新操作中生效。
圖8.採樣的統計信息
雖然圖6和圖8中的信息貌似相同,但細節上還是不同的。RANGE_ROWS中的實際實際行數被FLOAT類型的估計行數所替代。
總結
索引統計幫助SQL Sever優化查詢。
索引創建和重建時統計信息會自動生成。
索引統計信息分爲三部分,可以通過DBCC指定進行查看。
索引統計可以過時,過時的統計信息會導致不精確的查詢計劃。
默認情況下,索引統計信息會自動更新,你也可以不允許自動更新,也可以手動更新統計信息。
通常來說更新統計信息並不需要讀取索引的所有葉子節點,而是採樣部分索引。你可以通過參數來控制採樣數據的百分比。