由淺入深理解索引的實現(1)

yuanwen:http://www.mysqlops.com/2011/11/24/understanding_index.html

00 – 背景知識

- B-Tree & B+Tree

  http://en.wikipedia.org/wiki/B%2B_tree
  http://en.wikipedia.org/wiki/B-tree

- 折半查找(Binary Search)

  http://en.wikipedia.org/wiki/Binary_search_algorithm

- 數據庫的性能問題

  A. 磁盤IO性能非常低,嚴重的影響數據庫系統的性能。
  B. 磁盤順序讀寫比隨機讀寫的性能高很多。

- 數據的基本存儲結構

  A. 磁盤空間被劃分爲許多大小相同的塊(Block)或者頁(Page).
  B. 一個表的這些數據塊以鏈表的方式串聯在一起。
  C. 數據是以行(Row)爲單位一行一行的存放在磁盤上的塊中,如圖所示.
  D. 在訪問數據時,一次從磁盤中讀出或者寫入至少一個完整的Block。

                   Fig. 1


01 – 數據基本操作的實現

  基本操作包括:INSERT、UPDATE、DELETE、SELECT。

- SELECT

  A. 定位數據
  B. 讀出數據所在的塊,對數據加工
  C. 返回數據給用戶

- UPDATE、DELETE

  A. 定位數據
  B. 讀出數據所在的塊,修改數據
  C. 寫回磁盤

- INSERT

  A. 定位數據要插入的頁(如果數據需要排序)
  B. 讀出要插入的數據頁,插入數據.
  C. 寫回磁盤

如何定位數據?
- 表掃描(Table Scan)

  A. 從磁盤中依次讀出所有的數據塊,一行一行的進行數據匹配。
  B. 時間複雜度 是O(n), 如果所有的數據佔用了100個塊。儘管只查詢一行數據,
     也需要讀出所有100個塊的數據。
  C. 需要大量的磁盤IO操作,極大的影響了數據定位的性能。

因爲數據定位操作是所有數據操作必須的操作,數據定位操作的效率會直接影響所有的數據操作的效率。
因此我們開始思考,如何來減少磁盤的IO?
- 減少磁盤IO

  A. 減少數據佔用的磁盤空間
     壓縮算法、優化數據存儲結構
  B. 減少訪問數據的總量
     讀出或寫入的數據中,有一部分是數據操作所必須的,這部分稱作有效數據。剩餘的
     部分則不是數據操作必須的數據,稱爲無效數據。例如,查詢姓名是‘張三’的記錄。
     那麼這條記錄是有效記錄,其他記錄則是無效記錄。我們要努力減少無效數據的訪問。

02 – 索引的產生

- 鍵(Key)

  首先,我們發現在多數情況下,定位操作並不需要匹配整行數據。而是很規律的只匹配某一個
  或幾個列的值。 例如,圖中第1列就可以用來確定一條記錄。這些用來確定一條數據的列,統 
  稱爲鍵(Key).

        Fig. 2

- Dense Index

  根據減少無效數據訪問的原則,我們將鍵的值拿過來存放到獨立的塊中。並且爲每一個鍵值添
  加一個指針, 指向原來的數據塊。如圖所示,

            Fig. 3

  這就是‘索引’的祖先Dense Index. 當進行定位操作時,不再進行表掃描。而是進行
  索引掃描(Index Scan),依次讀出所有的索引塊,進行鍵值的匹配。當找到匹配的鍵值後,
  根據該行的指針直接讀取對應的數據塊,進行操作。假設一個塊中能存儲100行數據,
  10,000,000行的數據需要100,000個塊的存儲空間。假設鍵值列(+指針)佔用一行數據
  1/10的空間。那麼大約需要10,000個塊來存儲Dense索引。因此我們用大約1/10的額外存儲
  空間換來了大約全表掃描10倍的定位效率。

03 – 索引的進化

  在實際的應用中,這樣的定位效率仍然不能滿足需求。很多人可能已經想到了,通過排序和查找
  算法來減少IO的訪問。因此我們開始嘗試對Dense Index進行排序存儲,並且期望利用排序查
  找算法來減少磁盤IO。

- 折半塊查找

  A. 對Dense Index排序
  B. 需要一個數組按順序存儲索引塊地址。以塊爲單位,不存儲所有的行的地址。
  C. 這個索引塊地址數組,也要存儲到磁盤上。將其單獨存放在一個塊鏈中,如下圖所示。
  D. 折半查找的時間複雜度是O(log 2(N))。在上面的列子中,dense索引總共有10,000個塊。假設1個塊
     能存儲2000個指針,需要5個塊來存儲這個數組。通過折半塊查找,我們最多只需要讀取
     5(數組塊)+ 14(索引塊log 2(10000))+1(數據塊)=20個塊。

                    Fig. 4

 

- Sparse Index

  實現基於塊的折半查找時發現,讀出每個塊後只需要和第一行的鍵值匹配,就可以決定下一個塊
  的位置(方向)。 因此有效數據是每個塊(最後一個塊除外)的第一行的數據。還是根據減少無
  效數據IO的原則,將每一個塊的第一行的數據單獨拿出來,和索引數組的地址放到一起。這樣就
  可以直接在這個數組上進行折半查找了。如下圖所示,這個數組就進化成了Sparse Index

                    Fig. 5

  因爲Sparse Index和Dense Index的存儲結構是相同的,所以佔用的空間也相同。大約需
  要10個塊來存儲10000個Dense Index塊的地址和首行鍵值。通過Sparse索引,僅需要讀
  取10(Sparse塊)+1(Dense塊)+1(數據塊)=12個塊.

- 多層Sparse Index

  因爲Sparse Index本身是有序的,所以可以爲Sparse Index再建sparse Index。通過
  這個方法,一層一層的建立 Sparse Indexes,直到最上層的Sparse Index只佔用一個塊
  爲止,如下圖所示.

                   Fig. 6

  A. 這個最上層的Sparse Index稱作整個索引樹的根(root).
  B. 每次進行定位操作時,都從根開始查找。
  C. 每層索引只需要讀出一個塊。
  D. 最底層的Dense Index或數據稱作葉子(leaf).
  E. 每次查找都必須要搜索到葉子節點,才能定位到數據。
  F. 索引的層數稱作索引樹的高度(height).
  G. 索引的IO性能和索引樹的高度密切相關。索引樹越高,磁盤IO越多。

  在我們的例子中的Sparse Index,只有10個塊,因此我們只需要再建立一個Sparse Index.
  通過兩層Sparse Index和一層Dense Index查找時,只需讀取1+1+1+1=4個塊。

- Dense Index和Sparse Index的區別

  A. Dense Index包含所有數據的鍵值,但是Sparse Index僅包含部分鍵值。
     Sparse Index佔用更少的磁盤空間。
  B. Dense Index指向的數據可以是無序的,但是Sparse Index的數據必須是有序的。
  C. Sparse Index 可以用來做索引的索引,但是Dense Index不可以。
  D. 在數據是有序的時候,Sparse Index更有效。因此Dense Index僅用於無序的數據。
  E. 索引掃描(Index Scan)實際上是對Dense Index層進行遍歷。

- 簇索引(Clustered Index)和輔助索引(Secondary Index)

  如果數據本身是基於某個Key來排序的,那麼可以直接在數據上建立sparse索引,
  而不需要建立一個dense索引層(可以認爲數據就是dense索引層)。 如下圖所示:

Fig. 8
                Fig. 7

  這個索引就是我們常說的“Clustered Index”,而用來排序數據的鍵叫做主鍵Primary Key.

  A. 一個表只能有一個Clustered Index,因爲數據只能根據一個鍵排序.
  B. 用其他的鍵來建立索引樹時,必須要先建立一個dense索引層,在dense索引層上對此鍵的值
     進行排序。這樣的索引樹稱作Secondary Index.
  C. 一個表上可以有多個Secondary Index.
  D. 對簇索引進行遍歷,實際上就是對數據進行遍歷。因此簇索引的遍歷效率比輔組索引低。
     如SELECT count(*) 操作,使用輔組索引遍歷的效率更高。

- 範圍搜索(Range Search)

  由於鍵值是有序的,因此可以進行範圍查找。只需要將數據塊、Dense Index塊分別以雙向鏈表
  的方式進行連接, 就可以實現高效的範圍查找。如下圖所示:

Fig. 9
               Fig. 8
  範圍查找的過程:
  A. 選擇一個合適的邊界值,定位該值數據所在的塊
  B. 然後選擇合適的方向,在數據塊(或Dense Index塊)鏈中進行遍歷。
  C. 直到數據不滿足另一個邊界值,結束範圍查找。
是不是看着這個索引樹很眼熟?換個角度看看這個圖吧!

Fig. 9

這分明就是傳說中的B+Tree.
- 索引上的操作
  A. 插入鍵值
  B. 刪除鍵值
  C. 分裂一個節點
  D. 合併兩個節點
這些操作在教科書上都有介紹,這裏就不介紹了。
先寫到這吧,實在寫不動了,想明白容易,寫明白就難了。下一篇裏,打算談談標準B+Tree的幾個問題,以及在
實現過程中,B+Tree的一些變形。
很多知識來自於下面這兩本書。
Database Systems: The Complete Book (2nd Edition) ”
“Transaction Processing: Concepts and Techniques”
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章