標籤:
索引
1.B樹索引(B-TREE)
B樹索引是我們日常工作最最常用的索引,大家平時在工作中說的"索引"預設都是B樹索引;
索引其實很簡單,也很容易理解,用一本書的目錄來形容最為貼切了,B樹索引的結構跟圖書館的目錄也很像。
2.B樹索引的結構:
索引的頂層為根,它包括指向索引中下一層次的條目。下一層次為分支塊,它又指向位於索引中下一層索引中下一層次的塊,最底層的是分葉節點,它包含指向表行的索引條目。葉塊是雙向關聯的,這邊與按索引值升序或降序掃描索引;
3.索引葉條目的格式
一個索引條目包含以下組件:
條目頭:儲存列數和鎖定資訊
鍵列長度/值對:用於定義鍵中的列大小,後面跟隨列值(此類長度/值對的數目就是索引中的最大列數)。
4.索引葉條目的特性
在非分區表的B 樹索引中:
當多個行具有相同的索引值時,如果不壓縮索引,索引值會出現重複
當某行包含的所有鍵列為NULL 時,該行沒有對應的索引條目。因此,當WHERE 子句指定了NULL 時,將始終執行全表掃描。
5.對索引執行DML 操作的效果
對錶執行DML 操作時,Oracle 伺服器會維護所有索引。
下面說明對索引執行DML 命令產生的效果:
執行插入操作導致在相應塊中插入索引條目。
刪除一行只導致對索引條目進行邏輯刪除。已刪除行所佔用的空間不可供後面新的葉條目使用。
更新鍵列導致對索引進行邏輯刪除和插入。PCTFREE 設定對索引沒有影響,但建立時除外。即使索引塊的空間少於PCTFREE 指定的空間,也可以向索引塊添加新條目。
定義
B樹是一個專門的多路樹尤其適合在磁碟上使用而設計的。在B樹的每一節點可包含大量的密鑰。其每個節點也可以包含很多子樹。 B樹被設計在這眾多的方向的分支出來,並含有大量的密鑰的每個節點,使得樹的高度相對較小。這就是說,只有節點的少數必須從磁碟讀取檢索塊。其目的是獲得對資料的快速存取,和與磁碟機,這意味著讀出一個非常小的數量的記錄。需要注意的是一個大的節點大小(有很多的節點鍵)也與事實有一個磁碟機可以經常讀資料相當數量的一次配合。
用階定義的B樹
B 樹又叫平衡多路尋找樹。一棵m階的B 樹 (註:切勿簡單的認為一棵m階的B樹是m叉樹,雖然存在四叉樹,八叉樹,KD樹,及vp/R樹/R*樹/R+樹/X樹/M樹/線段樹/希爾伯特R樹/優先R樹等空間劃分樹,但與B樹完全不等同)的特性如下:
樹中每個結點最多含有m個孩子(m>=2);
除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函數);
若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點);
所有葉子結點都出現在同一層,葉子結點不包含任何關鍵字資訊(可以看做是外部接點或查詢失敗的接點,實際上這些結點不存在,指向這些結點的指標都為null);(讀者反饋@冷嶽:這裡有錯,葉子節點只是沒有孩子和指向孩子的指標,這些節點也存在,也有元素。@研究者July:其實,關鍵是把什麼當做葉子結點,因為如紅/黑樹狀結構中,每一個NULL指標即當做葉子結點,只是沒畫出來而已)。
每個非終端結點中包含有n個關鍵字資訊: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
a) Ki (i=1...n)為關鍵字,且關鍵字按順序升序排序K(i-1)< Ki。
b) Pi為指向子樹根的接點,且指標P(i-1)指向子樹種所有結點的關鍵字均小於Ki,但都大於K(i-1)。
c) 關鍵字的個數n必須滿足: [ceil(m/ 2)-1]<= n <= m-1。
一棵m階的B樹滿足下列條件:
1.每個結點至多有m棵子樹;
2.除根節點外,其它每個分支結點至少有?m/2?棵子樹;
3.根節點至少有兩棵子樹(除非B樹只包含一個結點);
4.所有分葉節點在同一層上。B樹的分葉節點可以看成一種外部結點,不包含任何資訊;
5.有j個孩子的非分葉節點恰好有j-1個關鍵碼,關鍵碼按遞增次序排列。
用度定義的B樹
針對上面的5點,再闡述下:B樹中每一個結點能包含的關鍵字(如之前上面的D H和Q T X)數有一個上界和下界。這個下界可以用一個稱作B樹的最小度數(演算法導論中文版上譯作度數,最小度數即內節點中節點最小孩子數目)m(m>=2)表示。
每個非根的內結點至多有m個子女,每個非根的結點必須至少含有m-1個關鍵字,如果樹是非空的,則根結點至少包含一個關鍵字;
每個結點可包含至多2m-1個關鍵字。所以一個內結點至多可有2m個子女。如果一個結點恰好有2m-1個關鍵字,我們就說這個結點是滿的(而稍後介紹的B*樹作為B樹的一種常用變形,B*樹中要求每個內結點至少為2/3滿,而不是像這裡的B樹所要求的至少半滿);
當關鍵字數m=2(t=2的意思是,mmin=2,m可以>=2)時的B樹是最簡單的(有很多人會因此誤認為B樹就是二叉尋找樹,但二叉尋找樹就是二叉尋找樹,B樹就是B樹,B樹是一棵含有m(m>=2)個關鍵字的平衡多路尋找樹),此時,每個內結點可能因此而含有2個、3個或4個子女,亦即一棵2-3-4樹,然而在實際中,通常採用大得多的t值。
B樹中的每個結點根據實際情況可以包含大量的關鍵字資訊和分支(當然是不能超過磁碟塊的大小,根據磁碟驅動(disk drives)的不同,一般塊的大小在1k~4k左右);這樣樹的深度降低了,這就意味著尋找一個元素只要很少結點從外存磁碟中讀入記憶體,很快訪問到要尋找的資料。
m階多路樹是一個有序的樹,其中每個節點最多有m個子節點。對於每個節點,如果k是子節點中的實際數目,則節點的密鑰數是k - 1。如果鍵和子樹被安排在一個搜尋樹的方式,那麼這就是所謂m的順序多路搜尋樹。例如,以下是順序4.注意的一個多路搜尋樹中的每個節點的第一行顯示的鑰匙,而第二行示出了指標的子節點。當然,在任何有用的應用將有與每個鍵相關聯的資料的記錄,以便在每一個節點的第一行可能的記錄數組,其中每個記錄都包含一個鍵及其關聯的資料。另一種方法是將有每個節點的第一行包含的記錄的陣列,其中每個記錄都包含了關聯的資料記錄,它是在另一檔案中找到一個鍵和一個記錄編號。當資料記錄大該最後方法經常被使用。這個例子將使用第一種方法。
這就是說,在鍵和子樹“排列在搜尋樹的前面”?假設我們定義節點如下:
typedef struct { int Count; // number of keys stored in the current node ItemType Key[3]; // array to hold the 3 keys long Branch[4]; // array of fake pointers (record numbers) } NodeType;
然後,4階多路搜尋樹必須滿足相關的鍵的順序如下條件:
在每個節點上的鍵是按升序排列。
在每一個給定的節點(稱之為節點)符合下列條件:
開始記錄Node.Branch子樹[0]只有不到Node.Key鍵[0]。
起始於記錄Node.Branch子樹[1]僅具有比Node.Key更大按鍵[0],並在同一時間小於Node.Key[1]。
起始於記錄Node.Branch子樹[2]僅具有比Node.Key更大的鍵[1],並在同一時間小於Node.Key[2]。
起始於記錄Node.Branch子樹[3]僅具有那些比Node.Key更大鍵[2]。
請注意,如果小於全數字鍵是在Node,這4個條件被截斷,使他們說話鍵和分支的適當數目。
這在推廣到具有多路與其它順序搜尋樹明顯的方式。
m階B樹是m階,使得多路搜尋樹:
所有葉結點都在底部;
所有內部節點(或許除了根節點)具有至少CEIL(M / 2)(非空)的子節點;
根節點可以具有少至2個子節點,如果它是一個內部節點,並能明顯地有沒有子節點如果根節點是葉(即,整個樹僅由根節點);
每個分葉節點(比如果它是葉根節點以外)必須包含至少CEIL(M / 2) - 1密鑰。
注意,CEIL(x)是所謂的整函數。它的值是大於或等於x的最小整數。從而CEIL(3)= 3,CEIL(3.35)= 4,CEIL(1.98)= 2,CEIL(5.01)= 6,CEIL(7)=7等
B樹是憑藉的事實,所有的分葉節點都必須在底部相當均衡的樹。條件(2)試圖保持樹通過堅持每個節點至少有一半是子節點的最大數目相當濃密。這將導致樹“扇出”,讓從根到葉的路徑很短,即使在包含大量資料的樹。
操作
以下是有5種順序的B樹例子,這意味著(其他根節點)所有的內部節點具有至少CEIL(5/2)= CEIL(2.5)= 3的子節點(並因此至少2個鍵)。當然,一個節點可以有子節點的最大數量是5(鑰匙的最大數量為4)。根據條件4中,每個分葉節點必須包含至少2密鑰。在實踐中B樹的順序遠遠大於5。
問:你如何在上面的樹來尋找的搜尋S和J?你會如何做一個排序的“按順序”遍曆,即,將產生按升序排列的字母遍曆? (一直做這樣的遍曆是低效的,因為這將需要大量的磁碟活動,因此會很慢!)
插入一個新塊
據克魯斯(查看下面的引用)的插入演算法過程如下:當插入一個塊,首先做一個搜尋它在B樹。如果該塊是不是已經在B樹,這個不成功的搜尋將在一個葉結點結束。如果在該分葉節點中仍存在空節點,那麼在此處插入新塊。注意,這可能需要一些現有鍵來移動一個到右側,以便劃分出空間給新的塊。相反,如果這個分葉節點是滿,使得沒有房間添加新的項,則該節點必須是“分裂”約一半的按鍵進入一個新的節點以這一個的右邊。中值(中)鍵被向上移動到父節點。(當然,如果該節點沒有餘地,則它可能不得不被分割為好。)請注意,添加到內部節點時,不僅可能,我們有一個位置移動某些鍵的右側,但相關的指標必須被向右移動為好。如果根節點是不斷分裂,中位鍵向上移動到一個新的根節點,從而導致樹由一個在高度增加。
讓我們的方式通過類似於由克魯斯給出一個例子。將下面的字母插入,變成順序為5的空B樹:C N G A H E K Q M F W L T Z D P R X Y S的三維p - [R X Y的訂單5是指節點最多可有5個孩子和4個按鍵。比根其他所有節點都必須有一個最低2鍵。前4個字母被插入到相同的節點,從而導致這樣的結果:
當我們嘗試插入H,我們發現在這個節點沒有空間,所以我們將其分成2個節點,移動平均商品G成一個新的根節點。請注意,在實踐中,我們剛剛離開A和C在當前節點,並將H和N插入一個新的節點,位於原節點的右側。
插入E,K和Q的操作可以繼續進行,而不需要任何分裂:
插入M需要一個分裂。另外,M恰好是中位元鍵等被向上移動到父節點。
然後無需任何分裂加入字母F,W,L和T。
當加入Z為,最右邊的葉結點必須被分割。中間塊T被向上移動到父節點。注意,通過向上移動平均鍵,樹被保持相當平衡,每個所得到2鍵的結點。
D的插入引起的最左邊的葉被分裂。 D恰好是中位元鍵等是一個向上移動到父節點。字母P,R,X和Y是那麼無需任何分裂加入:
最後,S被插入的時候,用N,P,Q和R分裂節點,發送位元Q上到父節點。然而,在父節點是滿的,所以它分割,發送位元m,最高以形成一個新的根的節點。注意,如何從舊的父節點入住3指標在包含D和G修訂後的節點。
刪除塊
當我們在最後一部分離開時,刪除H。當然,我們首先進行尋找,以找到H.由於H是在葉和葉片具有比鍵的最小數目越多,這很容易。我們搬過來且H已經在K與L以上,其中在K過。這表明:
接下來,刪除T.由於T不是一個葉子,我們發現它的後繼者(按升序排列的下一個塊),這恰好是W,移動W取代T.這樣,我們真的要做的是從葉結點,我們已經知道該怎麼做,因為這片葉子都有額外的鍵刪除W的在所有情況下,我們減少缺失到的缺失在一片葉結點,通過使用此方法。
接著,刪除R.雖然R是在葉結點,此葉結點不具有額外的鍵;刪除結果中的一個節點只有一個鍵,如果同級節點眼前的左這是不是順序為5的B樹接受或右側有一個額外的按鍵,我們可以借用父節點和移動兄弟結點。在我們的具體情況下,兄弟姐妹的權利有一個額外的鍵。因此,繼承WˉˉS(在發生刪除的節點的最後一個鍵)的,是從父結點向下移動,並且在X向上移動。(當然,在S上移動,使得W能夠在適當的位置被插入。)
最後,讓我們刪除E這一次會導致很多問題。雖然E是在葉結點,葉子已經沒有多餘的鍵,也沒有兄弟姐妹眼前的左邊或右邊。在這種情況下,葉結點可以與這些兩兄妹之一進行組合。這包括向下移動,這是與這些兩葉結點之間的父結點的關鍵。在我們的例子,讓我們結合含F用含有C葉結點我們也向下移動D。
當然,你立即看到父節點現在只包含一個鍵,G這是不能接受的。如果這個問題節點有一個兄弟到其直接向左或向右的有一把備用鑰匙,那麼我們將再次“借”的關鍵。假設為正確的兄弟姐妹(與QX的節點)在它有一個更重要的某處Q右側然後,我們就設法使得m下降到節點的子節點和移動Q上。然而,Q的舊左子樹隨後將不得不成為M的右子樹,換言之,N個P個節點,將通過該指標欄位被附加到M的新的位置的右側。因為在我們的例子中,我們也沒有辦法借用同級的關鍵,我們必須再次與兄弟結點相結合,並從父結點下移。在這種情況下,樹的高度由一個收縮。
一個例子
這裡是一個5中不同順序的B樹,讓我們試著從中刪除C結點。
我們首先找到直接後繼,這將移動D更換C.,太多的結點需要移動將使得我們的工作變得很難。
由於既沒有兄弟姐妹包含E中的節點的左側或右側有一個額外的鍵,我們必須節點與這些兩兄妹中的一個結合。讓我們從A B節點鞏固。
但現在含F節點沒有足夠的密鑰。然而,它的兄弟姐妹有一個額外的鍵。因此,我們從兄弟借M結點,移動到父結點,並帶來J結點加入F結點注意,在K結點和L結點被重新串連到J結點的右側。
另一個B-Tree的例子
下面是一個完全編碼的例子。有兩種方案:btmake建立一個B樹表,btread允許使用者從B樹查表(讀取)塊。在本實施例中,每個鍵是一個字和相關聯的資料是字的定義。編碼細節相當複雜。
itemtype.h
table.h(設定了一個抽象基類表)
btree.h(派生的B樹表類)
btree.cpp
btree.txt(資料使用的文字檔)
btmake.cpp(建立一個B樹表)
btread.cpp(從B樹表中讀取資料)
在btree.h你會發現,我們正在建立一個12順序的B樹。該B樹的每個節點是一個包含鍵數組(實際上密鑰和相關的資料),子節點指標數組(記錄記錄號),以及有多少密鑰實際上被儲存在該節點的計數。需要後者由於節點可能不完全充滿密鑰。
在同一個檔案你會發現BTTableClass類的聲明。請注意,有列出四個資料欄位:根節點的記錄數的多少個節點都在B樹的計數,每個節點(每當我們做輸入節點的/輸出需要的話)的位元組數,我們正在使用的整個當前節點。這最後一個領域給了我們一個方便的地方放置,我們在任何給定點工作的節點的資料。請記住,這個類的對象從抽象基類繼承三個資料欄位:檔案流,物品(包括字和它的定義的)中的表的數量,以及指示如果我們在開啟表一字元讀或寫入模式。你可以看到許多的這些目的和在一個單獨的繪圖其相關聯的資料檔案中的一個的細節。如果你願意,使用該圖的放大版本。
注意如何DEBUG的定義可以在btree.h被注釋了。如果你想運行已經包含調試代碼確保它是注釋。再看功能轉儲,檢查和CheckSubtree進一步的細節。在btmake.cpp筆記如何載入功能檢查,看看是否DEBUG已經被定義,如果是,執行某些調試代碼。即使在btree.cpp還有一個就是取決於是否不DEBUG被定義有條件地整理了一些調試代碼。在這裡,程式結束,並在節目中列印的關鍵點一個字母為正在執行什麼操作的跡象時,調試代碼列印整個B樹的轉儲。是字母和它們的含義如下:
的R - 從檔案執行讀
W - 做一個寫檔案
正在執行下推 - P
我 - 正在執行插入
正在執行分割 - S
沒有嘗試在這裡做出瞭解釋這一切樣本程式的細節,因為它是相當費時耗力。B樹的檢索功能的詳細解釋,以說明一些這一計劃的運作是可用的。如果需要,讀者可以更仔細地檢查等功能。大部分的功能其實是相當有下推功能,這是相當複雜的異常簡單。
變化
根據謝弗(參閱參考資料)的變化就可以了,B樹和大型商務資料庫通常用於提供快速存取的資料。事實上,他說,他們是“為需要插入,刪除,和主要範圍搜尋應??用程式的標準檔案組織”。稱為B +樹變體是常規的。另一種變型是B *樹,這是非常相似的B +樹,但試圖保持節點約三分之二充分最小。
在B +樹,資料記錄只儲存在樹葉。內部節點只儲存體金鑰。這些鍵用於指示一個搜尋到適當的葉。如果一個目標密鑰小於在內部節點的密鑰,然後只在其左側是跟著指標。如果一個目標密鑰是大於或等於在所述內部節點的密鑰,然後只在其右側後面的指標。葉片也串連在一起,使得所有的B +樹的鍵可以以升序遍曆,只需通過沿樹的底部水平此鏈表通過所有節點去。
當一個B +樹在磁碟上實現的,它是可能的葉含有鍵,指標對,其中該指標欄位指向與該鍵相關聯的資料的記錄。這使得資料檔案以從B +樹,其功能是作為一個“索引”給予的排序在資料檔案中的資料分別存在。這是怎樣的B +樹在資料庫中被使用。當然,指標是創紀錄的數字,我們在磁碟上建立動態資料結構時使用的典型假指標。請注意,此B +樹索引方案允許一個資料檔案到具有幾個這樣的指標,各由一個不同的密鑰欄位給出的排序。
作為一個例子,考慮一個B +樹的順序200,其葉子可以各自含有多達199鍵(約200)。讓我們假設根節點具有至少100名子節點(雖然我們知道它被允許至少有兩個)。 A2級的B +樹,以滿足這些假設可以儲存多達約10,000的記錄,因為至少有100葉結點,每片含至少99鍵(約100)。這種類型的A3級B +樹,最多可儲存約100萬項。 A4級B +樹,最多可儲存約100項。為了得到提高資料訪問速度,根節點通常儲存在主記憶體。甚至根的子節點可以容納在主儲存空間中。因此,人們可以發現,只有2或3磁碟讀取億關鍵之一。如果,由於是共同的,相關聯的資料記錄儲存在一個單獨的檔案中,有一個額外的讀得到與鍵有關的資料。此外,請注意,如果根節點有較少,我們假設100個子節點,這會進一步減慢尋找。
業務思想
B樹演算法很實用,但自己初次翻譯,感受頗深,寫的不明之處,歡迎交流討論。
Oracle索引的使用使得我們的操作簡便快捷,相信大家深有體會。
演算法,深入思考,可以讓人變得聰明哦!共勉!
參考:
CIS-Btree
B樹索引學習總結
淺談演算法和資料結構(10):平衡尋找樹之B樹
B樹索引和位元影像索引的結構介紹
CSDN部落格B-Tree索引
【Oracle】7.由B-Tree演算法談Oracle的索引