1. 程式人生 > >SQL Server索引進階第十一篇:索引碎片分析與解決(上)

SQL Server索引進階第十一篇:索引碎片分析與解決(上)

索引設計是資料庫設計中比較重要的一個環節,對資料庫的效能其中至關重要的作用,但是索引的設計卻又不是那麼容易的事情,效能也不是那麼輕易就獲取到的,很多的技術人員因為不恰當的建立索引,最後使得其效果適得其反,可以說“成也索引,敗也索引”。



相關有關索引碎片的問題,大家應該是聽過不少,也許也很多的朋友已經做了與之相關的工作。那我們今天就來看看這個問題。

為了更好的說明這個問題,我們首先來普及一些背景知識。


知識普及

我們都知道,資料庫中的每一個表要麼是堆表,要麼就是包含聚集索引的表,或者我們稱之為有序表。如果表是一個堆表,那麼在使用非聚集索引查詢資料的時候,會使用書籤查詢去底層的資料表中去檢索需要的資料,這個書籤查詢會通過每一個索引中包含的行標識(RID)去定位每一個底層資料表的資料行。如果表上面有聚集索引,那麼在使用非聚集索引查詢其他需要資料的時候,就會使用聚集索引鍵去定位底層的資料行。

我們也知道,索引是由索引頁組成的,索引中的每一個條目包含在頁中。每8個頁組成一個塊。

索引的層級是從底向上的,就是一個樹結構,最下面的就是第0層,也是葉節點。索引中的根節點處於整個索引的最上層。

如果要掃描整個索引,那麼就意味著必須要讀取頁節點中的每一個頁(要麼是資料頁,要麼是索引頁)。其中,每個頁都包含著一個指向它前面的頁和一個指向它後面也的指標。之前,我們也提過:如果單看某一層節點,其實就是一個雙向連結串列。還是上個圖,大家感受一下。


我們應該知道:頁(不管是資料頁,還是索引頁 ,還是其他的型別的頁)處於的邏輯順序和它的物理順便不一定就是一樣的,也就說,在A頁中的指標指向了它的下一個頁B,也就說A和B頁在邏輯上面是一起的,但是它們在物理上面可能不一樣,甚至B頁和A頁在物理上相隔幾百個頁。

如果在邏輯上面相連的頁在物理儲存級別相隔的越近,那麼在讀取這些頁的時候所花的I/O成本也就越小,因為產生磁碟的磁頭移動帶來的延遲。相反,如果他們的物理儲存順序和邏輯順序一致,那麼SQL Server在讀取的時候,就可以一次讀取,因為每次會讀取一個塊(8個頁)。

好了,普及知識之後,我們就來看看什麼是碎片。


什麼是索引碎片

索引碎片可以分為兩類:內部索引碎片和外部索引碎片。下面我們就來具體的看看而這之前的區別以及如何檢查。

內部索引碎片

每一個索引頁中都包含一些索引的條目(就類似資料頁包含很多的資料行一行),這一點我們在之前講過了的。但是,很多的時候,不是每個頁都包含了最大的條數。例如,一個頁的大小8k,也就是4096位元組,除去一些頁頭,頁尾等,還剩下8000多位元組,如果每個索引條目的大小事100位元組,那麼這個索引頁最大就可以包含80個條目,但是很多的情況下,卻沒有包含這麼多。

也就說,很多的時候,索引頁並沒有完全的填滿,或者這是問題,或許這麼我們特意這樣的,我們後續會提到。當我們談到索引碎片的時候,我們往往就是指這些索引頁沒有完全填滿。或者說的更加明白一點就是:我們原本是希望頁都被填滿的,但是隨著資料的增刪改,使得索引中的資料沒有填滿,結果如下:


圖不是很清晰,大家意會一下就行了。

我們可以使用
sys.dm_db_index_physical_stats來檢視相關的內部碎片的情況,執行查詢如下:

  1. SELECT IX.name AS 'Name'
  2. , PS.index_level AS 'Level'
  3. , PS.page_count AS 'Pages'
  4. , PS.avg_page_space_used_in_percent AS 'Page Fullness (%)'
  5. FROM sys.dm_db_index_physical_stats(
  6. DB_ID(),
  7. OBJECT_ID('Sales.SalesOrderDetail'),
  8. DEFAULT, DEFAULT, 'DETAILED') PS
  9. JOIN sys.indexes IX
  10. ON IX.OBJECT_ID = PS.OBJECT_ID AND IX.index_id = PS.index_id
複製程式碼

執行結果如圖:

我們可以看到每個索引的頁面的填充情況。

下面,我們再來講講外部索引碎片。

外部索引碎片

理解了上面的問題,這個外部索引碎片就好理解了,最簡單的說法就是:索引中的索引頁的邏輯順序和物理順序不一致。我們通過個圖對比的來看看。

9865.jpg(95.72 K)
9/9/2012 11:19:53 AM

在上圖中,一個索引包含了16個頁。但是這16頁不是包含在2個相連的塊中的,而是分佈在不同的地方,因為它們之前中的一些塊被其他的物件佔用了。這樣就導致了16個頁在物理上面不連續,這就是碎片。在讀取的時候,就會消耗額外的I/O。

和之前一樣,我們可以使用
sys.dm_db_index_physical_stats來檢視外部碎片的情況。但是這裡的引數值可能要發生變化了:之前在sys.dm_db_index_physical_stats最後一個引數值是'DETAILED',這裡我們的值是LIMITED或者Default。因為外部碎片關注的是索引頁之前的連續性問題,不關注每一個頁中的資料,此時只是部分的掃描,沒有必要全部的掃描。大家可以參看MSDN的去進一步的理解這些引數的含義。

查詢如下:

  1. SELECT IX.name AS 'Name'
  2. , PS.index_level AS 'Level'
  3. , PS.page_count AS 'Pages'
  4. , PS.avg_fragmentation_in_percent AS 'External Fragmentation (%)'
  5. , PS.fragment_count AS 'Fragments'
  6. , PS.avg_fragment_size_in_pages AS 'Avg Fragment Size'
  7. FROM sys.dm_db_index_physical_stats(
  8. DB_ID(),
  9. OBJECT_ID('Sales.SalesOrderDetail'),
  10. DEFAULT, DEFAULT, 'LIMITED') PS
  11. JOIN sys.indexes IX
  12. ON IX.OBJECT_ID = PS.OBJECT_ID AND IX.index_id = PS.index_id
複製程式碼

結果如下:

除了使用指令碼之外,我們還可以在SQL Server管理器中檢視,在某個索引上面右鍵,屬性,如下:

9869.jpg(87.17 K)
9/9/2012 11:19:53 AM

好,我們下一篇文章就來進一步的談談一下的內容:

是什麼導致了碎片

如何解決碎片問題

後文更加精彩!!!