1. 程式人生 > >PostgreSQL核心分析——BTree索引

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,其用於解決併發訪問下的問題,如當前所操作頁面正好是另一事務操作被分裂的頁面,則在當前頁面返回所得結果後,需要查詢其右兄弟頁面,來返回所得的正確結果。