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。
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).
- Dense Index
根據減少無效數據訪問的原則,我們將鍵的值拿過來存放到獨立的塊中。並且爲每一個鍵值添
加一個指針, 指向原來的數據塊。如圖所示,
這就是‘索引’的祖先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個塊。
- Sparse Index
實現基於塊的折半查找時發現,讀出每個塊後只需要和第一行的鍵值匹配,就可以決定下一個塊
的位置(方向)。 因此有效數據是每個塊(最後一個塊除外)的第一行的數據。還是根據減少無
效數據IO的原則,將每一個塊的第一行的數據單獨拿出來,和索引數組的地址放到一起。這樣就
可以直接在這個數組上進行折半查找了。如下圖所示,這個數組就進化成了Sparse Index。
因爲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只佔用一個塊
爲止,如下圖所示.
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索引層)。 如下圖所示:
這個索引就是我們常說的“Clustered Index”,而用來排序數據的鍵叫做主鍵Primary Key.
A. 一個表只能有一個Clustered Index,因爲數據只能根據一個鍵排序.
B. 用其他的鍵來建立索引樹時,必須要先建立一個dense索引層,在dense索引層上對此鍵的值
進行排序。這樣的索引樹稱作Secondary Index.
C. 一個表上可以有多個Secondary Index.
D. 對簇索引進行遍歷,實際上就是對數據進行遍歷。因此簇索引的遍歷效率比輔組索引低。
如SELECT count(*) 操作,使用輔組索引遍歷的效率更高。
- 範圍搜索(Range Search)
由於鍵值是有序的,因此可以進行範圍查找。只需要將數據塊、Dense Index塊分別以雙向鏈表
的方式進行連接, 就可以實現高效的範圍查找。如下圖所示:
- Fig. 8
- 範圍查找的過程:
- A. 選擇一個合適的邊界值,定位該值數據所在的塊
- B. 然後選擇合適的方向,在數據塊(或Dense Index塊)鏈中進行遍歷。
- C. 直到數據不滿足另一個邊界值,結束範圍查找。
- 是不是看着這個索引樹很眼熟?換個角度看看這個圖吧!
- 這分明就是傳說中的B+Tree.
- - 索引上的操作
- A. 插入鍵值
- B. 刪除鍵值
- C. 分裂一個節點
- D. 合併兩個節點
- 這些操作在教科書上都有介紹,這裏就不介紹了。
- 先寫到這吧,實在寫不動了,想明白容易,寫明白就難了。下一篇裏,打算談談標準B+Tree的幾個問題,以及在
- 實現過程中,B+Tree的一些變形。
- 很多知識來自於下面這兩本書。
- “Database Systems: The Complete Book (2nd Edition) ”
“Transaction Processing: Concepts and Techniques”