PostgreSQL核心分析——BTree索引
文中附圖參考至《PostgreSQL資料庫核心分析》
(一)概念描述
B+樹是一種索引資料結構,其一個特徵在於非葉子節點用於描述索引,而葉子節點指向具體的資料儲存位置。在PostgreSQL中,存在結構相似的BTree索引,該資料結構最先引用於《Effiicient Locking for Concurrent Operations on B-Trees》論文,一個新特徵在於,引入了“High Key”(下述HK)用於描述當前節點子節點的最大值。如下圖所示:
其中K1代表一個HK,其值等於P0及P0子節點的最大值,對於上述存在的2n個節點,每個節點都存在一個指標指向右兄弟節點,Pi的子節點取值範圍為(Ki-1,Ki]
(二)PostgreSQL的BTree索引結構
在PostgreSQL中,普通表的表文件組織結構如下圖
其中Linp結構用來指向檔案塊中的一個元組。Freespace是未分配的空閒空間,對於新插入頁面的元組及其對應的Linp元素從該空間進行分配,分配方式是Linp元素從Freespace的頭部分配,tuple從尾部分配。而PostgreSQL的索引結構,也是按照上述頁面結構進行儲存的。如下圖:
itup是排好序的索引元組,對於其如何完成排序將在之後的程式碼分析中進行介紹。linp用於索引itup,其儲存了每個itup在頁面中的實際位置。根據PostgreSQL中對BTree索引結構的描述,分為當前節點是否是最右節點兩種型別。由於非最右節點需要一個欄位來儲存HK,故當對一個頁面進行填充時,存在著以下兩種方式:
(1)當前節點為非最右節點
a.首先將itup3(最大的索引元組)複製到當前節點的右兄弟節點,然後將linp0指向itup3(HK)
b.去掉linp3。使用linp0來指向頁面中的HK。
(2)當前節點為最右節點
對於最右節點,其並不需要HK,故將每個linp遞減一個位置,linp3不再使用。
基於上述,PostgreSQL所實現的BTree索引組織結構如下圖:
總結上圖:
(1)對於非葉子節點,itup指向下一個節點,而對於葉子節點,itup指向實際物理儲存的位置。
(2)Special space中,實現了兩個指標,分配用於指向左右兄弟節點。
(3)根據BTree的特性,索引元組為有序,第一個葉子節點中itup3實際為最大索引元組,即HK,第二個葉子節點中itup1實際為最小索引元組,兩者相同,故指向了同一物理儲存位置。
(三)原始碼分析
- btbuild
索引建立的入口函式- BTBuildState buildstate
定義並初始化buildstate結構。用於儲存索引元組 - IndexBuildHeapScan
掃描表元組,並將其封裝為索引元組。函式返回構建好的索引元組個數,返回函式指標buildstate- while(... != NULL)
依次遍歷基表的所有元組
- while(... != NULL)
- _bt_leafbuild
將buildstate中得到的索引元組構建為索引結構- BTWriteState wstate
定義並初始化該結構。用於儲存整個索引建立過程的資訊。 - tuplesort_performsort
對索引元組進行排序- qsort_ssup or qsort_tuple
在記憶體中對索引元組進行排序。
- 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
將舊頁面的資訊寫入索引檔案。流程到這裡,肯定是遞迴函式已返回,由於舊頁面已完成填充,不會再進行修改,則將其寫入到索引檔案中
- Page opage = npage, npage = _bt_blnewpage()
- 頁面未滿
- _bt_sortaddup
將當前索引元組插入到頁面中 - state->btps_page = npage
設定當前頁面 - state->btps_blkno = nblko
設定當前磁碟塊 - state->btps_lastoff = last_off
設定當前頁面內偏移位置
- _bt_sortaddup
- if(頁面已滿)
- _bt_uppershutdown
構建每層節點的最右節點與父節點的連結關係- _bt_initmetapage
在最右節點與父節點關係構建完成後,定義元頁,每個btree索引結構由一個元頁結構記錄資訊
- _bt_initmetapage
- BTPageState *state
- BTWriteState wstate
- BTBuildState buildstate
- btinsert
在已建立的索引基礎上,插入一個新元素- index_form_tuple
將表元組首先封裝為索引元組 - _bt_doinsert
將索引元組插入到索引- _bt_mkscankey
計算元組的掃描鍵值scan_key - _bt_search
查詢包含索引元組的頁面- _bt_getroot
獲取索引結構的根節點 - for()
- _bt_moveright
併發性考慮 - if (當前節點為葉子節點)
跳出迴圈 - _bt_binsrch
不為葉子節點,在當前頁面找到合適的元組itup - new_stack
分配新的BTStack結構,將當前頁面資訊入棧
- _bt_moveright
- _bt_getroot
- if (唯一索引)
進行唯一性檢查 - _bt_findinsertloc
在當前頁面查詢索引元素合適的插入位置 - _bt_insertonpg
插入索引元組 - if (當前頁面沒有足夠的剩餘空間)
- _bt_findsplitloc
遍歷當前頁面節點,查詢最佳分裂點 - _bt_split
查詢到該分裂點,對其進行分裂 - _bt_insert_parent
把新節點資訊插入到父節點中
- _bt_findsplitloc
- else(當前頁面有存夠的剩餘空間)
直接插入節點
- _bt_mkscankey
- index_form_tuple
總結:上述給出了關於btree構建與在已構建的btree中插入新元素時的函式實現流程。實現邏輯思想參考(一)(二)。其中對於函式_bt_moveright,其用於解決併發訪問下的問題,如當前所操作頁面正好是另一事務操作被分裂的頁面,則在當前頁面返回所得結果後,需要查詢其右兄弟頁面,來返回所得的正確結果。