- 基本概念
- 基於磁碟的B+樹
- 查詢與索引
- 設計選擇
- 結點大小(Node Size)
- 合併閾值(Merge Thredshold)
- 變長鍵(Variable-length Keys)
- 結點內部搜尋(Intra-Node Search)
- 最佳化手段
- Pointer Swizzling
- Bε-trees
- Bulk Insert
- Prefix Compression
- Deduplication
- Suffix Truncation
基本概念
基於磁碟的B+樹
為什麼使用B+數進行資料訪問(Access Method):
- 天然有序,支援範圍查詢
- 支援遍歷所有資料,利用順序IO
- 時間複雜度為\(O(logn)\),滿足效能需求
- 相比於B樹,資料訪問都在葉子結點:磁碟空間利用率高;併發衝突減少
一個基礎的B+樹:
- 三類借點:根結點,中間結點,葉子結點
- 資料分佈:根結點和中間結點只儲存索引,葉子結點儲存資料
- 指標關係:父子指標,兄弟指標
基於磁碟的B+樹映象:
一個結點儲存在一個堆檔案(Heap File)頁中;頁ID(PageId)代替指標的作用。
-
鍵值聯合儲存
-
鍵值分別儲存
B+樹的葉子結點儲存實際資料,這個資料如何理解,取決於不同的資料庫實現:有些儲存RecordID,有些基於索引組織(Index-Organized Storage)的資料庫則直接儲存元組(Tuple)資料。
如果不瞭解RecordID,資料組織方式,可以參看這篇博文。
查詢與索引
最左字首匹配
有聯合索引<a,b,c>
,支援如下查詢條件
(a=1 AND b=2 AND c=3)
(a=1 AND b=2)
如果所有不滿足最左字首匹配原則,需要全表掃描。
如何處理重複鍵
-
加上RecordID使其變成唯一鍵
-
葉子結點溢位(沒有實際系統採用)
聚簇索引
- 一個表只能有一個聚簇索引
- 索引鍵和值儲存在一起
- 資料按照索引的鍵排序
- 運算元據時要同步操作索引
聚簇索引是非必須的,取決於資料庫具體實現,Mysql和SQLite中資料直接用聚簇索引組織。
用B+樹實現聚簇索引可以很方便地實現範圍查詢和便利,充分利用順序IO。
對於非聚簇索引,雖然索引的鍵有序,但是對應的資料在磁碟上不一定是順序儲存的,所以很有效的方式是先得到PageID,後根據PageID進行排序,最後獲取資料,充分利用順序IO。
設計選擇
結點大小(Node Size)
儲存裝置讀取資料越慢,越需要利用順序IO,結點就越大;
儲存裝置讀取資料越快,越需要減少冗餘資料讀取,結點就越小。
- HDD:~1MB
- SSD:~10KB
- In-Memory:~512B
合併閾值(Merge Thredshold)
結點中的鍵數量低於半滿的時候,不會立刻進行合併,而是允許小結點存在,然後再週期性地重建整棵樹。
PostgreSQL中稱其為不平衡的B+樹("non-balanced" B+Tree, nbtree)。
變長鍵(Variable-length Keys)
- 指標:鍵儲存指向實際資料的指標【無法利用順序IO,因為要跳轉去讀取指標內容】
- 變長結點
- 填充資料(Padding)
實際系統中的索引資料和堆檔案資料一樣,是能存結點就存結點中,是在存不下存指標。
結點內部搜尋(Intra-Node Search)
-
線性查詢:由於SIMD指令集存在,實際順序查詢,其實可以是批處理
-
二分查詢
-
插值法:鍵沒有間隙的時候(自增),可以直接計算出偏移
最佳化手段
Pointer Swizzling
基本思想:當一個物件從磁碟載入到記憶體時,將其磁碟地址轉換成記憶體地址(swizzling),以便程式在記憶體中直接透過指標訪問。
例子:比如主鍵索引的B+樹根結點讀取到Buffer Pool後,會被pin住,不被置換出去,所以此時可以直接用記憶體指標訪問根結點,省略用PageID問Buffer Pool要記憶體地址的步驟。
圖示:
Bε-trees
一種B+樹的寫最佳化。
基本思想:更新時不直接修改資料 ,而是記錄日誌(類似於log-structured data storage)。
日誌記錄在結點上,當結點日誌記錄滿以後,該結點的日誌下推到孩子結點。
Bulk Insert
基本思想:由底至頂建立B+樹,而不是由頂至底。
減少了插入時樹的結構變化,前提是需要預先排序資料。
Keys: 3, 7, 9, 13, 6, 1
Sorted Keys: 1, 3, 6, 7, 9, 13
Prefix Compression
基本思想:字典序壓縮字首。
Deduplication
基本思想:非唯一索引中避免重複儲存相同鍵。
Suffix Truncation
基本思想:中間結點只是起引路作用,所以儲存能辨識的最小字首即可。