天天看點

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

寫在前面:本篇文章需要你對索引和SQL中資料的存儲方式有一定了解.标題中進階兩個字僅僅是因為本篇文章需要我的T-SQL進階系列文章的一些内容作為基礎.

簡介

    在SQL Server中,存儲資料的最小機關是頁,每一頁所能容納的資料為8060位元組.而頁的組織方式是通過B樹結構(表上沒有聚集索引則為堆結構,不在本文讨論之列)如下圖:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

    在聚集索引B樹中,隻有葉子節點實際存儲資料,而其他根節點和中間節點僅僅用于存放查找葉子節點的資料.

    每一個葉子節點為一頁,每頁是不可分割的. 而SQL Server向每個頁記憶體儲資料的最小機關是表的行(Row).當葉子節點中新插入的行或更新的行使得葉子節點無法容納目前更新或者插入的行時,分頁就産生了.在分頁的過程中,就會産生碎片.

了解外部碎片

    首先,了解外部碎片的這個“外”是相對頁面來說的。外部碎片指的是由于分頁而産生的碎片.比如,我想在現有的聚集索引中插入一行,這行正好導緻現有的頁空間無法滿足容納新的行。進而導緻了分頁:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

     因為在SQL SERVER中,新的頁是随着資料的增長不斷産生的,而聚集索引要求行之間連續,是以很多情況下分頁後和原來的頁在磁盤上并不連續.

     這就是所謂的外部碎片.

     由于分頁會導緻資料在頁之間的移動,是以如果插入更新等操作經常需要導緻分頁,則會大大提升IO消耗,造成性能下降.

     而對于查找來說,在有特定搜尋條件,比如where子句有很細的限制或者傳回無序結果集時,外部碎片并不會對性能産生影響。但如果要傳回掃描聚集索引而查找連續頁面時,外部碎片就會産生性能上的影響.

     在SQL Server中,比頁更大的機關是區(Extent).一個區可以容納8個頁.區作為磁盤配置設定的實體單元.是以當頁分割如果跨區後,需要多次切區。需要更多的掃描.因為讀取連續資料時會不能預讀,進而造成額外的實體讀,增加磁盤IO.

了解内部碎片

    和外部碎片一樣,内部碎片的”内”也是相對頁來說的.下面我們來看一個例子:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

    我們建立一個表,這個表每個行由int(4位元組),char(999位元組)和varchar(0位元組組成),是以每行為1003個位元組,則8行占用空間1003*8=8024位元組加上一些内部開銷,可以容納在一個頁面中:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

    當我們随意更新某行中的col3字段後,造成頁内無法容納下新的資料,進而造成分頁:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

   分頁後的示意圖:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

    而當分頁時如果新的頁和目前頁實體上不連續,則還會造成外部碎片

内部碎片和外部碎片對于查詢性能的影響

    外部碎片對于性能的影響上面說過,主要是在于需要進行更多的跨區掃描,進而造成更多的IO操作.

    而内部碎片會造成資料行分布在更多的頁中,進而加重了掃描的頁樹,也會降低查詢性能.

    下面通過一個例子看一下,我們人為的為剛才那個表插入一些資料造成内部碎片:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

    通過檢視碎片,我們發現這時碎片已經達到了一個比較高的程度:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

    通過檢視對碎片整理之前和之後的IO,我們可以看出,IO大大下降了:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

對于碎片的解決辦法

    基本上所有解決辦法都是基于對索引的重建和整理,隻是方式不同

    1.删除索引并重建

       這種方式并不好.在删除索引期間,索引不可用.會導緻阻塞發生。而對于删除聚集索引,則會導緻對應的非聚集索引重建兩次(删除時重建,建立時再重建).雖然這種方法并不好,但是對于索引的整理最為有效

    2.使用DROP_EXISTING語句重建索引

       為了避免重建兩次索引,使用DROP_EXISTING語句重建索引,因為這個語句是原子性的,不會導緻非聚集索引重建兩次,但同樣的,這種方式也會造成阻塞

    3.如前面文章所示,使用ALTER INDEX REBUILD語句重建索引

       使用這個語句同樣也是重建索引,但是通過動态重建索引而不需要解除安裝并重建索引.是優于前兩種方法的,但依舊會造成阻塞。可以通過ONLINE關鍵字減少鎖,但會造成重建時間加長.

    4.使用ALTER INDEX REORGANIZE

       這種方式不會重建索引,也不會生成新的頁,僅僅是整理,當遇到加鎖的頁時跳過,是以不會造成阻塞。但同時,整理效果會差于前三種.

了解填充因子

      重建索引固然可以解決碎片的問題.但是重建索引的代價不僅僅是麻煩,還會造成阻塞。影響使用.而對于資料比較少的情況下,重建索引代價并不大。而當索引本身超過百兆的時候。重建索引的時間将會很讓人蛋疼.

      填充因子的作用正是如此。對于預設值來說,填充因子為0(0和100表示的是一個概念),則表示頁面可以100%使用。是以會遇到前面update或insert時,空間不足導緻分頁.通過設定填充因子,可以設定頁面的使用程度:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

      下面來看一個例子:

      還是上面那個表.我插入31條資料,則占4頁:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

     通過設定填充因子,頁被設定到了5頁上:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

     這時我再插入一頁,不會造成分頁:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

     上面的概念可以如下圖來解釋:

T-SQL查詢進階—SQL Server索引中的碎片和填充因子

      可以看出,使用填充因子會減少更新或者插入時的分頁次數,但由于需要更多的頁,則會對應的損失查找性能.

如何設定填充因子的值

    如何設定填充因子的值并沒有一個公式或者理念可以準确的設定。使用填充因子雖然可以減少更新或者插入時的分頁,但同時因為需要更多的頁,是以降低了查詢的性能和占用更多的磁盤空間.如何設定這個值進行trade-off需要根據具體的情況來看.

    具體情況要根據對于表的讀寫比例來看,我這裡給出我認為比較合适的值:

    1.當讀寫比例大于100:1時,不要設定填充因子,100%填充

    2.當寫的次數大于讀的次數時,設定50%-70%填充

    3.當讀寫比例位于兩者之間時80%-90%填充

總結