B+/-Tree原理
B-Tree介紹
B-Tree是一種多路搜索樹(并不是二叉的):
1.定義任意非葉子結點最多只有M個兒子;且M>2;
2.根結點的兒子數為[2, M];
3.除根結點以外的非葉子結點的兒子數為[M/2, M];
4.每個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)
5.非葉子結點的關鍵字個數=指向兒子的指針個數-1;
6.非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
7.非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小于K[1]的子樹,P[M]指向關鍵字大于K[M-1]的子樹,其它P[i]指向關鍵字屬于(K[i-1], K[i])的子樹;
8.所有葉子結點位于同一層;
如:(M=3)
B-樹的特性:
1.關鍵字集合分布在整顆樹中;
2.任何一個關鍵字出現且只出現在一個結點中;
3.搜索有可能在非葉子結點結束;
4.其搜索性能等價于在關鍵字全集內做一次二分查找;
5.自動層次控制;
B-樹的搜索:
從根結點開始,對結點內的關鍵字(有序)序列進行二分查找,如果命中則結束,否則進入查詢關鍵字所屬范圍的兒子結點;重復,直到所對應的兒子指針為空,或已經是葉子結點;
B-樹的插入:
首先在最低層的某個非終端結點中添加一個關鍵字,若該結點的關鍵字個數不超過M-1,則插入完成,否則要產生結點的“分裂”,
B-樹的刪除:
(1)被刪關鍵字所在結點中的關鍵字數目不小于ceil(M/2),則只需從該結點中刪去該關鍵字K[i]和相應指針P[i],樹的其它部分不變
(2)被刪關鍵字所在結點中的關鍵字數目等于ceil(M/2)-1,而與該結點相鄰的右兄弟(或左兄弟)結點中的關鍵字數目大于ceil(M/2)- 1,則需將其兄弟結點中的最小(或最大)的關鍵字上移至雙親結點中,而將雙親結點中小于(或大于)且緊靠該上移關鍵字的關鍵字下移至被刪關鍵字所在結點中。
(3)被刪關鍵字所在結點和其相鄰的兄弟結點中的關鍵字數目均等于ceil(M/2)-1。假設該結點有右兄弟,且其右兄弟結點地址由雙親結點中的指針 Ai所指,則在刪去關鍵字之后,它所在結點中剩余的關鍵字和指針,加上雙親結點中的關鍵字Ki一起,合并到 Ai所指兄弟結點中(若沒有右兄弟,則合并至左兄弟結點中)。
B+Tree介紹
B+樹是B-樹的變體,也是一種多路搜索樹:
1.其定義基本與B-樹同,除了:
2.非葉子結點的子樹指針與關鍵字個數相同;
3.非葉子結點的子樹指針P[i],指向關鍵字值屬于[K[i], K[i+1])的子樹(B-樹是開區間);
5.為所有葉子結點增加一個鏈指針;
6.所有關鍵字都在葉子結點出現;
如:(M=3)
B+的搜索與B-樹也基本相同,區別是B+樹只有達到葉子結點才命中(B-樹可以在非葉子結點命中),其性能也等價于在關鍵字全集做一次二分查找;
B+的特性:
1.所有關鍵字都出現在葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字恰好是有序的;
2.不可能在非葉子結點命中;
3.非葉子結點相當于是葉子結點的索引(稀疏索引),葉子結點相當于是存儲(關鍵字)數據的數據層;
4.更適合文件索引系統;
在linux中xfs文件全B+樹ext4 文件索引是B+樹,但目錄索引是bitmap。
MySQL索引實現
在MySQL中,索引屬于存儲引擎級別的概念,不同存儲引擎對索引的實現方式是不同的,本文主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式。
MyISAM索引實現
MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM索引的原理圖:
這里設表一共有三列,假設我們以Col1為主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:
同樣也是一顆B+Tree,data域保存數據記錄的地址。因此,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。
MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區分。
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上的一個輔助索引:
這里以英文字符的ASCII碼作為比較準則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
了解不同存儲引擎的索引實現方式對于正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現后,就很容易明白為什么不建議使用過長的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作為主鍵在InnoDB中不是個好主意,因為InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。
為什么選用B+/-Tree
一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對于內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數。
簡單點說說內存讀取,內存是由一系列的存儲單元組成的,每個存儲單元存儲固定大小的數據,且有一個唯一地址。當需要讀內存時,將地址信號放到地址總線上傳給內存,內存解析信號并定位到存儲單元,然后把該存儲單元上的數據放到數據總線上,回傳。
寫內存時,系統將要寫入的數據和單元地址分別放到數據總線和地址總線上,內存讀取兩個總線的內容,做相應的寫操作。
內存存取效率,跟次數有關,先讀取A數據還是后讀取A數據不會影響存取效率。而磁盤存取就不一樣了,磁盤I/O涉及機械操作。磁盤是由大小相同且同軸的圓形盤片組成,磁盤可以轉動(各個磁盤須同時轉動)。磁盤的一側有磁頭支架,磁頭支架固定了一組磁頭,每個磁頭負責存取一個磁盤的內容。磁頭不動,磁盤轉動,但磁臂可以前后動,用于讀取不同磁道上的數據。磁道就是以盤片為中心劃分出來的一系列同心環(如圖標紅那圈)。磁道又劃分為一個個小段,叫扇區,是磁盤的最小存儲單元。
為了盡量減少I/O操作,磁盤讀取每次都會預讀,大小通常為頁的整數倍。即使只需要讀取一個字節,磁盤也會讀取一頁的數據(通常為4K)放入內存,內存與磁盤以頁為單位交換數據。因為局部性原理認為,通常一個數據被用到,其附近的數據也會立馬被用到。