1. 程式人生 > >討論MySQL索引底層實現

討論MySQL索引底層實現

MySQL支援多種索引型別,如BTree索引,雜湊索引,全文索引等待。

本文主要討論BTree索引,這也是我們平時用得最多的索引。

索引的本質

MySQL官方對於索引的定義為:索引是幫助MySQL高效獲取資料的資料結構。即可以理解為:索引是資料結構。

我們知道,資料庫查詢是資料庫最主要的功能之一,我們都希望查詢資料的速度儘可能的快,因此資料庫系統的設計者會從查詢演算法的角度進行優化。最基本的查詢演算法當然是順序查詢,當然這種時間複雜度為O(n)的演算法在資料量很大時顯然是糟糕的,於是有了二分查詢、二叉樹查詢等。但是二分查詢要求被檢索資料有序,而二叉樹查詢只能應用於二叉查詢樹,但是資料本身的組織結構不可能完全滿足各種資料結構。所以,在資料之外,資料庫系統還維護者滿足特定查詢演算法的資料結構,這些資料結構以某種方式引用資料,這樣就可以在這些資料結構上實現高階查詢演算法。這種資料結構,就是索引。

B-Tree和B+Tree

目前大部分資料庫系統及檔案系統都採用B-Tree和B+Tree作為索引結構。

B-Tree

為了描述B-Tree,首先定義一條資料記錄為一個二元組[key,data],key為記錄的鍵值,對於不同的資料記錄,Key是不相同的;data為資料記錄除key外的資料。B-Tree滿足下列條件的資料結構:

1.d為大於1的正整數,稱為B-Tree的度;

2.h為一個正整數,稱為B-Tree的高度;

3.每個非葉子節點由n-1個key和n個指標組成,其中d<=n<=2d;

4.每個葉子節點最少包含一個key和兩個指標,最多包含2d-1個Key和2d個指標,葉節點的指標均為null;

5.所有葉節點具有相同的深度,等於樹高h;

6.key和指標相互隔離,節點兩端是指標;

7.一個節點中的key從左到右非遞減排列;

8.所有節點組成樹結構;

9.每個指標要麼為null,要麼指向另外一個節點;

10.如果某個指標在節點node最左邊且不為null,則其指向節點的所有key小於v(key1),其中v(key1)為node的第一個key的值;

11.如果某個指標在節點node最右邊且不為null,則其指向節點的所有key大於v(keym),其中v(keym)為node的最後一個key的值;

12.如果某個指標在節點node的左右相鄰key分別是keyi和keyi+1且不為null,則其指向節點的所有key小於v(keyi+1)且大於v(keyi)。


由於B-Tree的特性,在B-Tree中按Key檢索資料的演算法非常直觀:首先從根節點進行二分查詢,如果找到則返回對應節點的data,否則對相應區間的指標指向的節點遞迴進行查詢,直到找到節點或找到null指標,前者查詢成功,後者查詢失敗。

B-Tree上的查詢演算法如下:

<span style="font-size:14px;"><span style="font-family:Microsoft YaHei;font-size:14px;"><span style="font-size:14px;">BTree_Search(node,key){
		if(node == null){
			return null;
		}
		foreach(node.key){
			if(node.key[i] == key)
				return node.data[i];
			if(node.key[i] > key)
				return BTree_Search(point[i]->node);
		}
		return BTree_Search(point[i+1]->node);
	} 
	data = BTree_Search(root,my_key);</span></span></span>
由於插入刪除新的資料記錄會破壞B-Tree的性質,因此在插入刪除時,需要對樹進行一個分裂、合併、轉移等操作以保持B-Tree的性質。

B+Tree

B-Tree有很多變種,其中最常見的就是B+Tree,例如MySQL就普遍使用B+Tree實現其索引結構。

與B-Tree相比,B+Tree有以下不同點:

1.每個節點的指標上限為2d而不是2d+1

2.內節點不儲存data,只儲存Key;葉子節點不儲存指標。


由於並不是所有節點都具有相同的域,因此B+Tree中葉節點和內節點一般大小不同。這點與B-Tree不同,雖然B-Tree中不同節點存放的key和指標可能數量不一致,但是每個節點的域和上限是一致的,所以實現中B-Tree往往對每個節點申請同等大小的空間

一般來說,B+Tree比B-Tree更適合實現外儲存索引結構。

帶有順序訪問指標的B+Tree

一般在資料庫系統或檔案系統中使用B+Tree結構都是在經典的B+Tree的基礎上做了優化,增加了順序訪問指標。


如圖所示,在B+Tree的每個葉子節點增加一個指向相鄰葉子節點的指標,就形成了帶有順序訪問指標的B+Tree。這個優化的目的是為了提高區間訪問的效能。例如,在圖中要查詢key為18到49的所有資料記錄,當找到18後,只需順著節點和指標順序遍歷就可以一次性訪問到所有的資料節點,極大提高了區間查詢效率。

為什麼使用B-Tree(B+Tree)

一般來說,索引本身也很大,不可能全部儲存在記憶體中,因此索引往往以索引檔案的形式儲存在磁碟上。這樣的話,索引查詢過程就要產生I/O消耗,相對於記憶體存取,I/O存取的消耗要高几個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查詢過程中磁碟I/O操作次數的漸進複雜度。

主存存取原理

目前計算機使用的主存基本上都是隨機讀寫儲存器(RAM),現代RAM的結構和存取原理比較複雜,這裡抽象出一個十分簡單的存取模型來說明RAM的工作原理。


從抽象角度看,主存是一系列的儲存單元組成的矩陣,每個儲存單元儲存固定大小的資料。每個儲存單元有唯一的地址,現代主存的編址規則比較複雜,這裡將其簡化成一個二維地址:通過一個行地址和一個列地址可以唯一定位到一個儲存單元。

主存讀取過程如下:

當系統需要讀取主存時,則將地址訊號放到地址總線上傳給主存,主存讀到地址訊號後,解析訊號並定位到指定儲存單元,然後將此儲存單元資料放到資料匯流排上,供其他部件讀取。

寫主存的過程類似,系統將要寫入單元地址和資料分別放在地址匯流排和資料匯流排上,主存讀取兩個匯流排的內容,做相應的寫操作。

磁碟讀取原理

索引一般以檔案形式儲存在磁碟上,索引檢索需要磁碟I/O操作。與主存不同,磁碟I/O存在機械運動耗費,因此磁碟I/O的時間消耗是巨大的。


當需要從磁碟讀取資料時,系統會將資料邏輯地址傳給磁碟,磁碟的控制電路按照定址邏輯將邏輯地址翻譯成實體地址,即確定要讀的資料在哪個磁軌,哪個扇區。為了讀取這個扇區的資料,需要將磁頭放到這個扇區的上方,為了實現這一點,磁頭需要移動對準相應的磁軌,這個過程叫做尋道,所耗費的時間叫做尋道時間,然後磁碟旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫做旋轉時間。

區域性性原理和磁碟預讀

區域性性原理:當一個數據被用到時,其附近的資料也通常會馬上被使用。

程式執行期間所需要的資料通常比較集中。

由於磁碟順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有區域性性的程式來說,預讀可以提高I/O效率。 

預讀的長度一般為頁的整倍數,頁是計算機管理儲存器的邏輯塊,硬體及作業系統往往將主存和磁碟儲存區分割為連續的大小相等的塊,每個儲存稱為一頁(在許多作業系統,頁的大小通常為4k),主存和磁碟以頁為單位交換資料。當程式要讀取的資料不在主存中時,會觸發一個缺頁異常,此時系統會向磁碟發出讀盤訊號,磁碟會找到資料的起始位置並向後連續讀取一頁或幾頁載入記憶體中,然後異常返回,程式繼續執行。

B-/+Tree索引效能的分析

根據B-Tree的定義,可知檢索一次最多需要訪問h個節點。資料庫系統設計利用了磁碟預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,在實際實現B-Tree還需要如下技巧:

每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也儲存在一個頁裡,加之計算機儲存分配都是按頁對齊的,就實現了一個node只需一次I/O。

B-Tree中一次檢索最多需要h-1次I/O(根節點常駐記憶體),漸進複雜度為O(h)=O(logdN)。

MyISAM索引實現

MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是資料記錄的地址。


這裡設表一共有三列,假設我們以Col1為主鍵,則圖中是一個MyISAM表的主索引示意。可以看出MyISAM的索引檔案僅僅儲存資料記錄的地址。在MyISAM中,主索引和輔助索引在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重複。如果我們在Col2建立一個輔助索引,則索引結構為:


同樣也是一顆B+Tree,data域儲存資料記錄的地址。因此,MyISAM中索引檢索的演算法為首先按照B+Tree搜尋演算法搜尋索引,如果指定的Key存在,則取出其data域的值,然後以data域的值為地址,讀取相應資料記錄。

InnoDB索引的實現

雖然InnoDB也使用B+Tree作為索引結構,但具體實現方式與MyISAM截然不同。

第一個重大的區別是InnoDB的資料檔案本身就是索引檔案。從上文知道,MyISAM索引檔案和資料檔案是分離的,索引檔案僅儲存資料記錄的地址。而在InnoDB中,表資料檔案本身就是按B+Tree組織一個索引結構,這棵樹的葉節點data域儲存了完整的資料記錄。這個索引的key是資料表的主鍵,因此InnoDB表資料檔案本身就是主索引。


如圖是InnoDB主索引(同時也是資料檔案)的示意圖,可以看到葉節點包含了完整的資料記錄。這種索引叫做聚集索引。因為InnoDB的資料檔案本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯示指定,則MySQL系統會自動選擇一個可以唯一標識資料記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含欄位作為主鍵,這個欄位長度為6個位元組,型別為長整型。

第二個與MyISAM索引的不同是InnoDB的輔助索引data域儲存相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。例如,下圖為定義在Col3上的一個輔助索引:


聚集索引這種實現方式使得按主鍵的搜尋十分高效,但輔助索引搜尋需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到索引中檢索獲得記錄。