文中附圖參考至《PostgreSQL數據庫內核分析》node
(一)概念描述數據庫
B+樹是一種索引數據結構,其一個特徵在於非葉子節點用於描述索引,而葉子節點指向具體的數據存儲位置。在PostgreSQL中,存在結構類似的BTree索引,該數據結構最早引用於《Effiicient Locking for Concurrent Operations on B-Trees》論文,一個新特徵在於,引入了「High Key」(下述HK)用於描述當前節點子節點的最大值。以下圖所示:數組
![](http://static.javashuo.com/static/loading.gif)
其中K1表明一個HK,其值等於P0及P0子節點的最大值,對於上述存在的2n個節點,每一個節點都存在一個指針指向右兄弟節點,Pi的子節點取值範圍爲(Ki-1,Ki]數據結構
(二)PostgreSQL的BTree索引結構併發
在PostgreSQL中,普通表的表文件組織結構以下圖app
![](http://static.javashuo.com/static/loading.gif)
其中Linp結構用來指向文件塊中的一個元組。Freespace是未分配的空閒空間,對於新插入頁面的元組及其對應的Linp元素從該空間進行分配,分配方式是Linp元素從Freespace的頭部分配,tuple從尾部分配。而PostgreSQL的索引結構,也是按照上述頁面結構進行存儲的。以下圖:函數
![](http://static.javashuo.com/static/loading.gif)
itup是排好序的索引元組,對於其如何完成排序將在以後的代碼分析中進行介紹。linp用於索引itup,其存儲了每一個itup在頁面中的實際位置。根據PostgreSQL中對BTree索引結構的描述,分爲當前節點是不是最右節點兩種類型。因爲非最右節點須要一個字段來保存HK,故當對一個頁面進行填充時,存在着如下兩種方式:源碼分析
(1)當前節點爲非最右節點ui
![](http://static.javashuo.com/static/loading.gif)
a.首先將itup3(最大的索引元組)複製到當前節點的右兄弟節點,而後將linp0指向itup3(HK)spa
b.去掉linp3。使用linp0來指向頁面中的HK。
(2)當前節點爲最右節點
![](http://static.javashuo.com/static/loading.gif)
對於最右節點,其並不須要HK,故將每一個linp遞減一個位置,linp3再也不使用。
基於上述,PostgreSQL所實現的BTree索引組織結構以下圖:
![](http://static.javashuo.com/static/loading.gif)
總結上圖:
(1)對於非葉子節點,itup指向下一個節點,而對於葉子節點,itup指向實際物理存儲的位置。
(2)Special space中,實現了兩個指針,分配用於指向左右兄弟節點。
(3)根據BTree的特性,索引元組爲有序,第一個葉子節點中itup3實際爲最大索引元組,即HK,第二個葉子節點中itup1實際爲最小索引元組,二者相同,故指向了同一物理存儲位置。
(三)源碼分析
- btbuild
索引建立的入口函數
- BTBuildState buildstate
定義並初始化buildstate結構。用於保存索引元組
- IndexBuildHeapScan
掃描表元組,並將其封裝爲索引元組。函數返回構建好的索引元組個數,返回函數指針buildstate
- while(... != NULL)
依次遍歷基表的全部元組
- _bt_leafbuild
將buildstate中獲得的索引元組構建爲索引結構
- BTWriteState wstate
定義並初始化該結構。用於保存整個索引建立過程的信息。
- tuplesort_performsort
對索引元組進行排序
- qsort_ssup or qsort_tuple
在內存中對索引元組進行排序。
- _bt_load
對已排好序的索引元組,順序讀出將其插入到btree索引結構中
- BTPageState *state
定義並初始化該結構,在btree中,每一層僅有一個BTPageState結構,記錄每層節點的信息
- if (merge)
若是spool2不爲空,即if條件成立,則將spool與spool2進行歸併排序
- else
spool2爲空,則索引元組都存放在spool結構中
- while(依次取出spool中的每一個索引元組)
- _bt_buildadd
將每一個索引元組添加到索引結構
- if(頁面已滿)
- Page opage = npage, npage = _bt_blnewpage()
設置舊頁面爲當前頁面,並從新分配新頁面做爲右兄弟節點
- _bt_buildadd
這裏比較巧妙的利用遞歸,從當前已分配的頁面開始,完成後續索引數組的插入
- _bt_blwritepage
將舊頁面的信息寫入索引文件。流程到這裏,確定是遞歸函數已返回,因爲舊頁面已完成填充,不會再進行修改,則將其寫入到索引文件中
- 頁面未滿
- _bt_sortaddup
將當前索引元組插入到頁面中
- state->btps_page = npage
設置當前頁面
- state->btps_blkno = nblko
設置當前磁盤塊
- state->btps_lastoff = last_off
設置當前頁面內偏移位置
- _bt_uppershutdown
構建每層節點的最右節點與父節點的連接關係
- _bt_initmetapage
在最右節點與父節點關係構建完成後,定義元頁,每一個btree索引結構由一個元頁結構記錄信息
- btinsert
在已創建的索引基礎上,插入一個新元素
- index_form_tuple
將表元組首先封裝爲索引元組
- _bt_doinsert
將索引元組插入到索引
- _bt_mkscankey
計算元組的掃描鍵值scan_key
- _bt_search
查找包含索引元組的頁面
- _bt_getroot
獲取索引結構的根節點
- for()
- _bt_moveright
併發性考慮
- if (當前節點爲葉子節點)
跳出循環
- _bt_binsrch
不爲葉子節點,在當前頁面找到合適的元組itup
- new_stack
分配新的BTStack結構,將當前頁面信息入棧
- if (惟一索引)
進行惟一性檢查
- _bt_findinsertloc
在當前頁面查找索引元素合適的插入位置
- _bt_insertonpg
插入索引元組
- if (當前頁面沒有足夠的剩餘空間)
- _bt_findsplitloc
遍歷當前頁面節點,查找最佳分裂點
- _bt_split
查找到該分裂點,對其進行分裂
- _bt_insert_parent
把新節點信息插入到父節點中
- else(當前頁面有存夠的剩餘空間)
直接插入節點
總結:上述給出了關於btree構建與在已構建的btree中插入新元素時的函數實現流程。實現邏輯思想參考(一)(二)。其中對於函數_bt_moveright,其用於解決併發訪問下的問題,如當前所操做頁面正好是另外一事務操做被分裂的頁面,則在當前頁面返回所得結果後,須要查找其右兄弟頁面,來返回所得的正確結果。