【摘要】
最近在看Mysql的存儲引擎中索引的優化,神馬是索引,支持啥索引.全是浮雲,目前Mysql的MyISAM和InnoDB都支持B-Tree索引,InnoDB還支持B+Tree索引,Memory還支持Hash.今天從最基礎的學起,學習了解BTree,B-Tree和B+Tree。
【主題】
- B-Tree 介紹
- B-Tree 特性搜索插入等
- B+Tree 介紹
- B*Tree 介紹
【內容】
1. B-Tree 介紹
1970年,R.Bayer和E.mccreight提出了一種適用於外查找的樹,它是一種平衡的多叉樹,稱為B樹,其定義如下:
一棵m階的B樹滿足下列條件:
- 樹中每個結點至多有m個孩子;
- 除根結點和葉子結點外,其它每個結點至少有m/2個孩子;
- 若根結點不是葉子結點,則至少有2個孩子;
- 所有葉子結點(失敗節點)都出現在同一層,葉子結點不包含任何關鍵字信息;
- 所有非終端結點中包含下列信息數據 ( n, A0 , K1 , A1 , K2 , A2 , … , Kn , An ),其中: Ki (i=1,…,n)為關鍵字,且Ki < Ki+1 , Ai (i=0,…,n)為指向子樹根結點的指針, n為關鍵字的個數
- 非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;
在B樹中,每個結點中關鍵字從小到大排列,並且當該結點的孩子是非葉子結點時,該k-1個關鍵字正好是k個孩子包含的關鍵字的值域的分划。因為葉子結點不包含關鍵字,所以可以把葉子結點看成在樹里實際上並不存在外部結點,指向這些外部結點的指針為空,葉子結點的數目正好等於樹中所包含的關鍵字總個數加1。B樹中的一個包含n個關鍵字,n+1個指針的結點的一般形式為: (n,P0,K1,P1,K2,P2,…,Kn,Pn)其中,Ki為關鍵字,K1 <K2 <… <Kn, Pi 是指向包括Ki到Ki+1之間的關鍵字的子樹的指針。
-- 圖1 B-tree示意圖
2. B-Tree特性
2.1 B-Tree 特性
- 關鍵字集合分布在整顆樹中;
- 任何一個關鍵字出現且只出現在一個結點中;
- 搜索有可能在非葉子結點結束;
- 其搜索性能等價於在關鍵字全集內做一次二分查找;
- 自動層次控制;
2.2 B-Tree搜索原理
B-樹的搜索,從根結點開始,對結點內的關鍵字(有序)序列進行二分查找,如果命中則結束,否則進入查詢關鍵字所屬范圍的兒子結點;重復,直到所對應的兒子指針為空,或已經是葉子結點;因此,B-Tree的查找過程是一個順指針查找結點和在結點的關鍵字中進行查找的交叉進行的過程。
-- 圖2 高度與關鍵碼的計算過程
2.3 B-Tree 插入
B-樹是從空樹起,逐個插入關鍵碼而生成的。
在B-樹,每個非失敗結點的關鍵碼個數都在[ m/2 -1, m-1]之間。插入在某個葉結點開始。如果在關鍵碼插入后結點中的關鍵碼個數超出了上界 m-1,則結點需要“分裂”,否則可以直接插入。
實現結點“分裂”的原則是:
設結點 A 中已經有 m-1 個關鍵碼,當再插入一個關鍵碼后結點中的狀態為( m, A0, K1, A1, K2, A2, ……, Km, Am)其中 Ki < Ki+1, 1 =< m
這時必須把結點 p 分裂成兩個結點 p 和 q,它們包含的信息分別為:
結點 p:( m/2 -1, A0, K1, A1, ……, Km/2 -1, Am/2 -1)
結點 q:(m - m/2, Am/2, Km/2+1, Am/2+1, ……, Km, Am)
位於中間的關鍵碼 Km/2 與指向新結點 q 的指針形成一個二元組 ( Km/2, q ),插入到這兩個結點的雙親結點中去。
3. B+Tree
3.1 B+Tree定義
B+樹可以看作是B樹的一種變形,在實現文件索引結構方面比B樹使用得更普遍。
一棵 m 階B+樹可以定義如下:
- 樹中每個非葉結點最多有 m 棵子樹;
- 根結點 (非葉結點) 至少有 2 棵子樹。除根結點外, 其它的非葉結點至少有 ém/2ù 棵子樹;有 n 棵子樹的非葉結點有 n-1 個關鍵碼。
- 所有葉結點都處於同一層次上,包含了全部關鍵碼及指向相應數據對象存放地址的指針,且葉結點本身按關鍵碼從小到大順序鏈接;
- 每個葉結點中的子樹棵數 n 可以多於 m,可以少於 m,視關鍵碼字節數及對象地址指針字節數而定。
- 若設結點可容納最大關鍵碼數為 m1,則指向對象的地址指針也有 m1 個。
- 結點中的子樹棵數 n 應滿足 n 屬於[m1/2, m1]
- 若根結點同時又是葉結點,則結點格式同葉結點。
- 所有的非葉結點可以看成是索引部分,結點中關鍵碼 Ki 與指向子樹的指針 Pi 構成對子樹 (即下一層索引塊) 的索引項 ( Ki, Pi ),Ki 是子樹中最小的關鍵碼。
- 特別地,子樹指針 P0 所指子樹上所有關鍵碼均小於 K1。結點格式同B樹。
- 葉結點中存放的是對實際數據對象的索引。
- 在B+樹中有兩個頭指針:一個指向B+樹的根結點,一個指向關鍵碼最小的葉結點。
B+Tree與B-Tree區別
- 非葉子結點的子樹指針與關鍵字個數相同;
- 非葉子結點的子樹指針P[i],指向關鍵字值屬於[K[i], K[i+1])的子樹(B-樹是開區間);
- 為所有葉子結點增加一個鏈指針;
- 所有關鍵字都在葉子結點出現;
對B+樹進行兩種搜索運算
- 循葉結點鏈順序搜索
- 另一種是從根結點開始,進行自頂向下,直至葉結點的隨機搜索。
-- 圖3 B+Tree示意圖
3.2 B+Tree特性
B+Tree的搜索與B-Tree也基本相同,區別是B+Tree只有達到葉子結點才命中(B-Tree可以在非葉子結點命中),其性能也等價於在關鍵字全集做一次二分查找;
B+Tree的特性
- 所有關鍵字都出現在葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字恰好是有序的;
- 不可能在非葉子結點命中;
- 非葉子結點相當於是葉子結點的索引(稀疏索引),葉子結點相當於是存儲(關鍵字)數據的數據層;
- 更適合文件索引系統
4. B*Tree
4.1 B*Tree
B*Tree是B+樹的變體,在B+Tree的非根和非葉子結點再增加指向兄弟的指針;
-- 圖4 B*Tree示意圖
B*樹定義了非葉子結點關鍵字個數至少為(2/3)*M,即塊的最低使用率為2/3(代替B+樹的1/2);
B+樹的分裂:當一個結點滿時,分配一個新的結點,並將原結點中1/2的數據復制到新結點,最后在父結點中增加新結點的指針;B+樹的分裂只影響原結點和父結點,而不會影響兄弟結點,所以它不需要指向兄弟的指針;
B*樹的分裂:當一個結點滿時,如果它的下一個兄弟結點未滿,那么將一部分數據移到兄弟結點中,再在原結點插入關鍵字,最后修改父結點中兄弟結點的關鍵字(因為兄弟結點的關鍵字范圍改變了);如果兄弟也滿了,則在原結點與兄弟結點之間增加新結點,並各復制1/3的數據到新結點,最后在父結點增加新結點的指針;
所以,B*樹分配新結點的概率比B+樹要低,空間使用率更高;
【結束】
自我總結:
- B-樹:多路搜索樹,每個結點存儲M/2到M個關鍵字,非葉子結點存儲指向關鍵字范圍的子結點;所有關鍵字在整顆樹中出現,且只出現一次,非葉子結點可以命中;
- B+樹:在B-樹基礎上,為葉子結點增加鏈表指針,所有關鍵字都在葉子結點中出現,非葉子結點作為葉子結點的索引;B+樹總是到葉子結點才命中;
- B*樹:在B+樹基礎上,為非葉子結點也增加鏈表指針,將結點的最低利用率從1/2提高到2/3;
B-樹和B+樹的應用:數據搜索和數據庫索引
1 .B-樹定義
B-樹是一種平衡的多路查找樹,它在文件系統中很有用。
定義:一棵m 階的B-樹,或者為空樹,或為滿足下列特性的m 叉樹:
⑴樹中每個結點至多有m 棵子樹;
⑵若根結點不是葉子結點,則至少有兩棵子樹;
⑶除根結點之外的所有非終端結點至少有[m/2] 棵子樹;
⑷所有的非終端結點中包含以下信息數據:
(n,A0,K1,A1,K2,…,Kn,An)
其中:Ki(i=1,2,…,n)為關鍵碼,且Ki<Ki+1,
Ai 為指向子樹根結點的指針(i=0,1,…,n),且指針Ai-1 所指子樹中所有結點的關鍵碼均小於Ki (i=1,2,…,n),An 所指子樹中所有結點的關鍵碼均大於Kn.
n 為關鍵碼的個數。
⑸所有的葉子結點都出現在同一層次上,並且不帶信息(可以看作是外部結點或查找失敗的結點,實際上這些結點不存在,指向這些結點的指針為空)。
即所有葉節點具有相同的深度,等於樹高度。
如一棵四階B-樹,其深度為4.
B-樹的查找類似二叉排序樹的查找,所不同的是B-樹每個結點上是多關鍵碼的有序表,在到達某個結點時,先在有序表中查找,若找到,則查找成功;否則,到按照對應的指針信息指向的子樹中去查找,當到達葉子結點時,則說明樹中沒有對應的關鍵碼。
在上圖的B-樹上查找關鍵字47的過程如下:
1)首先從更開始,根據根節點指針找到 *節點,因為 *a 節點中只有一個關鍵字,且給定值47 > 關鍵字35,則若存在必在指針A1所指的子樹內。
2)順指針找到 *c節點,該節點有兩個關鍵字(43和 78),而43 < 47 < 78,若存在比在指針A1所指的子樹中。
3)同樣,順指針找到 *g節點,在該節點找到關鍵字47,查找成功。
2. 查找算法
- typedef int KeyType ;
- #define m 5 /*B 樹的階,暫設為5*/
- typedef struct Node{
- int keynum; /* 結點中關鍵碼的個數,即結點的大小*/
- struct Node *parent; /*指向雙親結點*/
- KeyType key[m+1]; /*關鍵碼向量,0 號單元未用*/
- struct Node *ptr[m+1]; /*子樹指針向量*/
- Record *recptr[m+1]; /*記錄指針向量*/
- }NodeType; /*B 樹結點類型*/
- typedef struct{
- NodeType *pt; /*指向找到的結點*/
- int i; /*在結點中的關鍵碼序號,結點序號區間[1…m]*/
- int tag; /* 1:查找成功,0:查找失敗*/
- }Result; /*B 樹的查找結果類型*/
- Result SearchBTree(NodeType *t,KeyType kx)
- {
- /*在m 階B 樹t 上查找關鍵碼kx,反回(pt,i,tag)。若查找成功,則特征值tag=1,*/
- /*指針pt 所指結點中第i 個關鍵碼等於kx;否則,特征值tag=0,等於kx 的關鍵碼記錄*/
- /*應插入在指針pt 所指結點中第i 個和第i+1 個關鍵碼之間*/
- p=t;q=NULL;found=FALSE;i=0; /*初始化,p 指向待查結點,q 指向p 的雙親*/
- while(p&&!found)
- { n=p->keynum;i=Search(p,kx); /*在p-->key[1…keynum]中查找*/
- if(i>0&&p->key[i]= =kx) found=TRUE; /*找到*/
- else {q=p;p=p->ptr[i];}
- }
- if(found) return (p,i,1); /*查找成功*/
- else return (q,i,0); /*查找不成功,反回kx 的插入位置信息*/
- }
B- 樹查找算法分析
從查找算法中可以看出, 在B- 樹中進行查找包含兩種基本操作:
( 1) 在B- 樹中查找結點;
( 2) 在結點中查找關鍵字。
由於B- 樹通常存儲在磁盤上, 則前一查找操作是在磁盤上進行的, 而后一查找操作是在內存中進行的, 即在磁盤上找到指針p 所指結點后, 先將結點中的信息讀入內存, 然后再利用順序查找或折半查找查詢等於K 的關鍵字。顯然, 在磁盤上進行一次查找比在內存中進行一次查找的時間消耗多得多.
因此, 在磁盤上進行查找的次數、即待查找關鍵字所在結點在B- 樹上的層次樹, 是決定B樹查找效率的首要因素
那么,對含有n 個關鍵碼的m 階B-樹,最壞情況下達到多深呢?可按二叉平衡樹進行類似分析。首先,討論m 階B-數各層上的最少結點數。
由B樹定義:B樹包含n個關鍵字。因此有n+1個樹葉都在第J+1 層。
1)第一層為根,至少一個結點,根至少有兩個孩子,因此在第二層至少有兩個結點。
2)除根和樹葉外,其它結點至少有[m/2]個孩子,因此第三層至少有2*[m/2]個結點,在第四層至少有2*[m/2]2 個結點…
3)那么在第J+1層至少有2*[m/2]J-1個結點,而J+1層的結點為葉子結點,於是葉子結點的個數n+1。有:
也就是說在n個關鍵字的B樹查找,從根節點到關鍵字所在的節點所涉及的節點數不超過:
3.B-樹的插入
B-樹的生成也是從空樹起,逐個插入關鍵字而得。但由於B-樹結點中的關鍵字個數必須≥ceil(m/2)-1,因此,每次插入一個關鍵字不是在樹中添加一個葉子結點,而是首先在最低層的某個非終端結點中添加一個關鍵字,若該結點的關鍵字個數不超過m-1,則插入完成,否則要產生結點的“分裂”,
如圖(a) 為3階的B-樹(圖中略去F結點(即葉子結點)),假設需依次插入關鍵字30,26,85。
1) 首先通過查找確定插入的位置。由根*a 起進行查找,確定30應插入的在*d 節點中。由於*d 中關鍵字數目不超過2(即m-1),故第一個關鍵字插入完成:如(b)
2) 同樣,通過查找確定關鍵字26亦應插入 *d. 由於*d節點關鍵字數目超過2,此時需要將 *d分裂成兩個節點,關鍵字26及其前、后兩個指針仍保留在 *d 節點中,而關鍵字37 及其前、后兩個指針存儲到新的產生的節點 *d` 中。同時將關鍵字30 和指示節點 *d `的指針插入到其雙親的節點中。由於 *b節點中的關鍵字數目沒有超過2,則插入完成.如(c)(d)
3) (e) -(g) 為插入85后;
插入算法:
- int InserBTree(NodeType **t,KeyType kx,NodeType *q,int i){
- /* 在m 階B 樹*t 上結點*q 的key[i],key[i+1]之間插入關鍵碼kx*/
- /*若引起結點過大,則沿雙親鏈進行必要的結點分裂調整,使*t仍為m 階B 樹*/
- x=kx;ap=NULL;finished=FALSE;
- while(q&&!finished)
- {
- Insert(q,i,x,ap); /*將x 和ap 分別插入到q->key[i+1]和q->ptr[i+1]*/
- if(q->keynum<m) finished=TRUE; /*插入完成*/
- else
- { /*分裂結點*p*/
- s=m/2;split(q,ap);x=q->key[s];
- /*將q->key[s+1…m],q->ptr[s…m]和q->recptr[s+1…m]移入新結點*ap*/
- q=q->parent;
- if(q) i=Search(q,kx); /*在雙親結點*q 中查找kx 的插入位置*/
- }
- }
- if(!finished) /*(*t)是空樹或根結點已分裂為*q*和ap*/
- NewRoot(t,q,x,ap); /*生成含信息(t,x,ap)的新的根結點*t,原*t 和ap 為子樹指針*/
- }
4. B-樹的刪除
反之,若在B-樹上刪除一個關鍵字,則首先應找到該關鍵字所在結點,並從中刪除之,若該結點為最下層的非終端結點,且其中的關鍵字數目不少於ceil(m/2),則刪除完成,否則要進行“合並”結點的操作。假若所刪關鍵字為非終端結點中的Ki,則可以指針Ai所指子樹中的最小關鍵字Y替代Ki,然后在相應的結點中刪去Y。例如,在下圖 圖4.1( a)的B-樹上刪去45,可以*f結點中的50替代45,然后在*f結點中刪去50。
圖4.1( a)
因此,下面我們可以只需討論刪除最下層非終端結點中的關鍵字的情形。有下列三種可能:
(1)被刪關鍵字所在結點中的關鍵字數目不小於ceil(m/2),則只需從該結點中刪去該關鍵字Ki和相應指針Ai,樹的其它部分不變,例如,從圖 圖4.1( a)所示B-樹中刪去關鍵字12,刪除后的B-樹如圖 圖4.2( a)所示:
圖4.2( a)
(2)被刪關鍵字所在結點中的關鍵字數目等於ceil(m/2)-1,而與該結點相鄰的右兄弟(或左兄弟)結點中的關鍵字數目大於ceil(m/2)-1,則需將其兄弟結點中的最小(或最大)的關鍵字上移至雙親結點中,而將雙親結點中小於(或大於)且緊靠該上移關鍵字的關鍵字下移至被刪關鍵字所在結點中。
[例如],從圖圖4.2( a)中刪去50,需將其右兄弟結點中的61上移至*e結點中,而將*e結點中的53移至*f,從而使*f和*g中關鍵字數目均不小於ceil(m-1)-1,而雙親結點中的關鍵字數目不變,如圖圖4.2(b)所示。
圖4.2(b)
(3)被刪關鍵字所在結點和其相鄰的兄弟結點中的關鍵字數目均等於ceil(m/2)-1。假設該結點有右兄弟,且其右兄弟結點地址由雙親結點中的指針Ai所指,則在刪去關鍵字之后,它所在結點中剩余的關鍵字和指針,加上雙親結點中的關鍵字Ki一起,合並到 Ai所指兄弟結點中(若沒有右兄弟,則合並至左兄弟結點中)。
[例如],從圖4.2(b)所示 B-樹中刪去53,則應刪去*f結點,並將*f中的剩余信息(指針“空”)和雙親*e結點中的 61一起合並到右兄弟結點*g中。刪除后的樹如圖4.2(c)所示。
圖4.2(c)
如果因此使雙親結點中的關鍵字數目小於ceil(m/2)-1,則依次類推。
[例如],在 圖4.2(c)的B-樹中刪去關鍵字37之后,雙親b結點中剩余信息(“指針c”)應和其雙親*a結點中關鍵字45一起合並至右兄弟結點*e中,刪除后的B-樹如圖 4.2(d)所示。
圖 4.2(d)
B-樹主要應用在文件系統
為了將大型數據庫文件存儲在硬盤上 以減少訪問硬盤次數為目的 在此提出了一種平衡多路查找樹——B-樹結構 由其性能分析可知它的檢索效率是相當高的 為了提高 B-樹性能’還有很多種B-樹的變型,力圖對B-樹進行改進
B+樹
B+樹是應文件系統所需而產生的一種B-樹的變形樹。一棵m 階的B+樹和m 階的B-
樹的差異在於:
⑴有n 棵子樹的結點中含有n 個關鍵碼;
⑵所有的葉子結點中包含了全部關鍵碼的信息,及指向含有這些關鍵碼記錄的指針,且
葉子結點本身依關鍵碼的大小自小而大的順序鏈接。
⑶所有的非終端結點可以看成是索引部分,結點中僅含有其子樹根結點中最大(或最小)關鍵碼。

樹,不管查找成功與否,每次查找都是走了一條從根到葉子結點的路徑。
B+樹在數據庫中的應用
1. 索引在數據庫中的作用
在數據庫系統的使用過程當中,數據的查詢是使用最頻繁的一種數據操作。
最基本的查詢算法當然是順序查找(linear search),遍歷表然后逐行匹配行值是否等於待查找的關鍵字,其時間復雜度為O(n)。但時間復雜度為O(n)的算法規模小的表,負載輕的數據庫,也能有好的性能。 但是數據增大的時候,時間復雜度為O(n)的算法顯然是糟糕的,性能就很快下降了。
好在計算機科學的發展提供了很多更優秀的查找算法,例如二分查找(binary search)、二叉樹查找(binary tree search)等。如果稍微分析一下會發現,每種查找算法都只能應用於特定的數據結構之上,例如二分查找要求被檢索數據有序,而二叉樹查找只能應用於二叉查找樹上,但是數據本身的組織結構不可能完全滿足各種數據結構(例如,理論上不可能同時將兩列都按順序進行組織),所以,在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
索引是對數據庫表 中一個或多個列的值進行排序的結構。與在表 中搜索所有的行相比,索引用指針 指向存儲在表中指定列的數據值,然后根據指定的次序排列這些指針,有助於更快地獲取信息。通常情 況下 ,只有當經常查詢索引列中的數據時 ,才需要在表上創建索引。索引將占用磁盤空間,並且影響數 據更新的速度。但是在多數情況下 ,索引所帶來的數據檢索速度優勢大大超過它的不足之處。
2. B+樹在數據庫索引中的應用
目前大部分數據庫系統及文件系統都采用B-Tree或其變種B+Tree作為索引結構
1)在數據庫索引的應用
在數據庫索引的應用中,B+樹按照下列方式進行組織 :
① 葉結點的組織方式 。B+樹的查找鍵 是數據文件的主鍵 ,且索引是稠密的。也就是說 ,葉結點 中為數據文件的第一個記錄設有一個鍵、指針對 ,該數據文件可以按主鍵排序,也可以不按主鍵排序 ;數據文件按主鍵排序,且 B +樹是稀疏索引 , 在葉結點中為數據文件的每一個塊設有一個鍵、指針對 ;數據文件不按鍵屬性排序 ,且該屬性是 B +樹 的查找鍵 , 葉結點中為數據文件里出現的每個屬性K設有一個鍵 、 指針對 , 其中指針執行排序鍵值為 K的 記錄中的第一個。
② 非葉結點 的組織方式。B+樹 中的非葉結點形成 了葉結點上的一個多級稀疏索引。 每個非葉結點中至少有ceil( m/2 ) 個指針 , 至多有 m 個指針 。
2)B+樹索引的插入和刪除
①在向數據庫中插入新的數據時,同時也需要向數據庫索引中插入相應的索引鍵值 ,則需要向 B+樹 中插入新的鍵值。即上面我們提到的B-樹插入算法。
②當從數據庫中刪除數據時,同時也需要從數據庫索引中刪除相應的索引鍵值 ,則需要從 B+樹 中刪 除該鍵值 。即B-樹刪除算法
為什么使用B-Tree(B+Tree)
二叉查找樹進化品種的紅黑樹等數據結構也可以用來實現索引,但是文件系統及數據庫系統普遍采用B-/+Tree作為索引結構。
一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數。為什么使用B-/+Tree,還跟磁盤存取原理有關。
局部性原理與磁盤預讀
由於存儲介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分分之一,因此為了提高效率,要盡量減少磁盤I/O。為了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個字節,磁盤也會從這個位置開始,順序向后讀取一定長度的數據放入內存。這樣做的理論依據是計算機科學中著名的局部性原理:
當一個數據被用到時,其附近的數據也通常會馬上被使用。
程序運行期間所需要的數據通常比較集中。
由於磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對於具有局部性的程序來說,預讀可以提高I/O效率。
預讀的長度一般為頁(page)的整倍數。頁是計算機管理存儲器的邏輯塊,硬件及操作系統往往將主存和磁盤存儲區分割為連續的大小相等的塊,每個存儲塊稱為一頁(在許多操作系統中,頁得大小通常為4k),主存和磁盤以頁為單位交換數據。當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向后連續讀取一頁或幾頁載入內存中,然后異常返回,程序繼續運行。
我們上面分析B-/+Tree檢索一次最多需要訪問節點:
h =
數據庫系統巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,在實際實現B- Tree還需要使用如下技巧:
每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,就實現了一個node只需一次I/O。
B-Tree中一次檢索最多需要h-1次I/O(根節點常駐內存),漸進復雜度為O(h)=O(logmN)。一般實際應用中,m是非常大的數字,通常超過100,因此h非常小(通常不超過3)。
綜上所述,用B-Tree作為索引結構效率是非常高的。
而紅黑樹這種結構,h明顯要深的多。由於邏輯上很近的節點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的I/O漸進復雜度也為O(h),效率明顯比B-Tree差很多。
MySQL的B-Tree索引(技術上說B+Tree)
在 MySQL 中,主要有四種類型的索引,分別為: B-Tree 索引, Hash 索引, Fulltext 索引和 R-Tree 索引。我們主要分析B-Tree 索引。
B-Tree 索引是 MySQL 數據庫中使用最為頻繁的索引類型,除了 Archive 存儲引擎之外的其他所有的存儲引擎都支持 B-Tree 索引。Archive 引擎直到 MySQL 5.1 才支持索引,而且只支持索引單個 AUTO_INCREMENT 列。
不僅僅在 MySQL 中是如此,實際上在其他的很多數據庫管理系統中B-Tree 索引也同樣是作為最主要的索引類型,這主要是因為 B-Tree 索引的存儲結構在數據庫的數據檢索中有非常優異的表現。
一般來說, MySQL 中的 B-Tree 索引的物理文件大多都是以 Balance Tree 的結構來存儲的,也就是所有實際需要的數據都存放於 Tree 的 Leaf Node(葉子節點) ,而且到任何一個 Leaf Node 的最短路徑的長度都是完全相同的,所以我們大家都稱之為 B-Tree 索引。當然,可能各種數據庫(或 MySQL 的各種存儲引擎)在存放自己的 B-Tree 索引的時候會對存儲結構稍作改造。如 Innodb 存儲引擎的 B-Tree 索引實際使用的存儲結構實際上是 B+Tree,也就是在 B-Tree 數據結構的基礎上做了很小的改造,在每一個Leaf Node 上面出了存放索引鍵的相關信息之外,還存儲了指向與該 Leaf Node 相鄰的后一個 LeafNode 的指針信息(增加了順序訪問指針),這主要是為了加快檢索多個相鄰 Leaf Node 的效率考慮。
下面主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式:
1. MyISAM索引實現:
1)主鍵索引:
MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是數據記錄的地址。下圖是MyISAM主鍵索引的原理圖:
(圖myisam1)
這里設表一共有三列,假設我們以Col1為主鍵,圖myisam1是一個MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件僅僅保存數據記錄的地址。
2)輔助索引(Secondary key)
在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:
同樣也是一顆B+Tree,data域保存數據記錄的地址。因此,MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。
MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區分。
2. InnoDB索引實現
然InnoDB也使用B+Tree作為索引結構,但具體實現方式卻與MyISAM截然不同.
1)主鍵索引:
MyISAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。
(圖inndb主鍵索引)
(圖inndb主鍵索引)是InnoDB主索引(同時也是數據文件)的示意圖,可以看到葉節點包含了完整的數據記錄。這種索引叫做聚集索引。因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。
2). InnoDB的輔助索引
InnoDB的所有輔助索引都引用主鍵作為data域。例如,下圖為定義在Col3上的一個輔助索引:
InnoDB 表是基於聚簇索引建立的。因此InnoDB 的索引能提供一種非常快速的主鍵查找性能。不過,它的輔助索引(Secondary Index, 也就是非主鍵索引)也會包含主鍵列,所以,如果主鍵定義的比較大,其他索引也將很大。如果想在表上定義 、很多索引,則爭取盡量把主鍵定義得小一些。InnoDB 不會壓縮索引。
文字符的ASCII碼作為比較准則。聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。
不同存儲引擎的索引實現方式對於正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實現后,就很容易明白為什么不建議使用過長的字段作為主鍵,因為所有輔助索引都引用主索引,過長的主索引會令輔助索引變得過大。再例如,用非單調的字段作為主鍵在InnoDB中不是個好主意,因為InnoDB數據文件本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時數據文件為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段作為主鍵則是一個很好的選擇。
InnoDB索引和MyISAM索引的區別:
一是主索引的區別,InnoDB的數據文件本身就是索引文件。而MyISAM的索引和數據是分開的。
二是輔助索引的區別:InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。而MyISAM的輔助索引和主索引沒有多大區別。
MySql索引算法原理解析(通俗易懂,只講B-tree)
https://www.cnblogs.com/gym333/p/6877023.html