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)
        依次遍歷基表的所有元組
    • _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,其用於解決並發訪問下的問題,如當前所操作頁面正好是另一事務操作被分裂的頁面,則在當前頁面返回所得結果后,需要查找其右兄弟頁面,來返回所得的正確結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM