SQLite學習筆記(15)-B-tree(1)
B-Tree
B-tree的主要功能就是索引,它維護著各個頁面之間的複雜的關係,便於快速找到所需資料。
B-Tree使得VDBE可以在O(logN)下查詢,插入和刪除資料,以及O(1)下雙向遍歷結果集。B-Tree不會直接讀寫磁碟,它僅僅維護著頁面 (pages)之間的關係。當B-TREE需要頁面或者修改頁面時,它就會呼叫Pager。當修改頁面時,pager保證原始頁面首先寫入日誌檔案,當它完成寫操作時,pager根據事務狀態決定如何做。B-tree不直接讀寫檔案,而是通過page cache這個緩衝模組讀寫檔案對於效能是有重要意義的。
2.5.1 資料庫檔案格式(DatabaseFile Format)
從邏輯上來說,一個SQLite資料庫檔案由多個多重Btree構成。一個數據庫由許多B-tree構成——每一個表和索引都有一個B-tree(注:索引採用B-tree,而表採用B+tree,這主要是表和索引的需求不同以及B-tree和B+tree的結構不同決定的:B+tree的所有葉子節點包含了全部關鍵字資訊,而且可以有兩種順序查詢。而B-tree更適合用來作索引)。
2.5.1.1 頁
資料庫檔案包括一個或多個頁。頁的大小可以是512B到64KB之間的2的任意次方。
資料庫中所有的頁面都按從1開始順序標記,其最大頁數目為2147483646 ( - 2)[6]。資料庫中的頁主要分為以下幾類:
- The lock-byte page
- A freelist page
- A freelist trunk page
- A freelist leaf page
- A b-tree page
- A table b-tree interior page
- A table b-tree leaf page
- An index b-tree interior page
- An index b-tree leaf page
- A payload overflow page
- A pointer map page
資料庫中載入到記憶體中的頁的定義位於btreeInt.h,其結構如下:
<pre name="code" class="cpp">structMemPage { u8 isInit; /* True if previously initialized.MUST BE FIRST! */ u8 nOverflow; /* Number of overflow cell bodies inaCell[] */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table*/ u8 noPayload; /* True if internal intKey page (thusw/o data) */ u8 leaf; /* True if a leaf page */ u8 hdrOffset; /* 100 for page 1. 0 otherwise */ u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */ u8 max1bytePayload; /* min(maxLocal,127) */ u8 bBusy; /* Prevent endless loops on corruptdatabase files */ u16 maxLocal; /* Copy of BtShared.maxLocal orBtShared.maxLeaf */ u16 minLocal; /* Copy of BtShared.minLocal orBtShared.minLeaf */ u16 cellOffset; /* Index in aData of first cell pointer*/ u16 nFree; /* Number of free bytes on the page*/ u16 nCell; /* Number of cells on this page,local and ovfl */ u16 maskPage; /* Mask for page offset */ u16 aiOvfl[5]; /* Insert the i-th overflow cell beforethe aiOvfl-th ** non-overflow cell */ u8 *apOvfl[5]; /* Pointers to the body of overflowcells */ BtShared *pBt; /* Pointer to BtShared that this page ispart of */ u8 *aData; /* Pointer to disk image of the page data */ u8 *aDataEnd; /* One byte past the end of usable data*/ u8 *aCellIdx; /* The cell index area */ u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */ DbPage *pDbPage; /* Pager page handle */ u16 (*xCellSize)(MemPage*,u8*); /* cellSizePtr method */ void (*xParseCell)(MemPage*,u8*,CellInfo*);/* btreeParseCell method */ Pgno pgno; /* Page number for this page */ };
2.5.1.2 檔案頭
資料庫中第一個頁(page 1)永遠是Btree頁。而每個表或索引的第1個頁稱為根頁,所有表或索引的根頁編號都儲存在系統表sqlite_master中,表sqlite_master的根頁為page 1。Page 1的前100個位元組是一個對資料庫檔案進行描述的“檔案頭”。它包括資料庫的版本、格式的版本、頁大小、編碼等所有建立資料庫時設定的永久性引數。這個特殊檔案頭的文件在btreeInt.h中,格式如表2.2所示。
表2.2 Page 1的前100位元組
偏移量 |
大小 |
說明 |
0 |
16 |
The header string: "SQLite format 3\000" |
16 |
2 |
The database page size in bytes. Must be a power of two between 512 and 65536. |
18 |
1 |
File format write version. 1 for legacy; 2 for WAL. |
19 |
1 |
File format read version. 1 for legacy; 2 for WAL. |
20 |
1 |
Bytes of unused "reserved" space at the end of each page. Usually 0. |
21 |
1 |
Maximum embedded payload fraction. Must be 64. |
22 |
1 |
Minimum embedded payload fraction. Must be 32. |
23 |
1 |
Leaf payload fraction. Must be 32. |
24 |
4 |
File change counter. |
28 |
4 |
Size of the database file in pages. The "in-header database size". |
32 |
4 |
Page number of the first freelist trunk page. |
36 |
4 |
Total number of freelist pages. |
40 |
4 |
The schema cookie. |
44 |
4 |
The schema format number. Supported schema formats are 1, 2, 3, and 4. |
48 |
4 |
Default page cache size. |
52 |
4 |
The page number of the largest root b-tree page when in auto-vacuum or incremental-vacuum modes, or zero otherwise. |
56 |
4 |
The database text encoding. A value of 1 means UTF-8. A value of 2 means UTF-16le. A value of 3 means UTF-16be. |
60 |
4 |
The "user version" as read and set by the user_version pragma. |
64 |
4 |
True (non-zero) for incremental-vacuum mode. False (zero) otherwise. |
72 |
20 |
Reserved for expansion. Must be zero. |
2.5.1.3 鎖位元組頁(TheLock-Byte Page)
鎖位元組頁是資料庫檔案中的單獨一頁,它包含偏移量在1073741824和1073742335位元組之間的位元組。不大於1073741824位元組的資料庫檔案沒有鎖位元組頁。只有大於1073741824位元組的資料庫檔案含有一頁鎖位元組頁。
SQLite不使用鎖位元組頁,它是專門留給OS在VFS實現中的資料庫檔案鎖定原語。
鎖位元組頁定義在btreeInt.h,其結構如下:
structBtLock {
Btree *pBtree; /* Btree handle holding this lock */
Pgno iTable; /* Root page of table */
u8 eLock; /* READ_LOCK or WRITE_LOCK *
BtLock *pNext; /* Next in BtShared.pLock list */
};
2.5.1.4 空閒頁連結串列(TheFreelist)
一個數據庫檔案或多或少含有一些沒有使用的頁。這些頁可能是由於其中的資訊被資料庫刪除產生的。這些空閒頁被存放在空閒頁連結串列,當需要額外的頁時就會被重用。
空閒頁連結串列是由freelist trunkpage相互連線組成的連結串列,而每個freelist trunk page是由freelist leaf page(可以為零)組成的。
一個freelist trunk page是由一個4位元組大端整數陣列組成。陣列是在可用空間中放入儘可能多的整數。它的最小可用空間是480位元組,所以陣列最少可以放120整數。陣列中的第一個整數指向連結串列中的下一個freelist trunk page,如果為零就代表這是連結串列中的最後一個freelist trunk page。第二個整數代表所含有的freelist leaf page指標的數目。將第二個整數稱為L,如果L大於零,那麼陣列中2到L+1之間的每個整數就表示一個freelist leaf page。
在freelist leaf page中則什麼都沒有。
2.5.1.5 B-tree頁(B-treePages)
SQLite中使用了兩種B-tree。其中一種是b+tree,將所用的資料儲存在葉子節點,在SQLite中被稱為table b-tree。另一種是最初未經修改的b-tree,枝幹節點和葉子節點都含有關鍵字和資料,在SQLite中被稱為index b-tree。
Btree頁內部以單元為單位來組織資料,一個單元包含一個(或部分,當使用溢位頁時)payload(也稱為Btree記錄)。由於各類資料大小各不相同,每個單元的大小也就是可變的,所以Btree頁內部的空間需要進行動態分配(程式內部動態分配,不是動態申請空間),單元是Btree頁內部進行空間分配和回收的基本單位。每一個Btree頁包括三個部分:頁頭,單元指標陣列以及單元內容區。Page1除包括頁頭外,還包括100位元組的檔案頭,其結構如圖2.6所示。頁內所有單元的內容集中在頁的底部,稱為“單元內容區”,由下向上增長。而單元指標陣列則含有單元內容區每個單元的偏移量(2 byte),順序排列,偏於對單元內容進行插入和查詢,由上而下增長。
檔案頭(只有page 1有) |
頁頭 |
單元指標陣列 |
未分配空間 |
單元內容區 |
圖2.6 Btree頁結構
其頁頭格式如表2.3所示。其中Flags定義了Btree頁的型別。標識leaf表示這個也是否有孩子。標識zerodata表示這個頁只有記錄沒有資料。標識intkey表示整數關鍵字存放在key size entry,而不是payload區域。
表2.3 Btree頁頭格式
偏移量 |
大小 |
說明 |
0 |
1 |
Flags. 1: intkey, 2: zerodata, 4: leafdata, 8: leaf |
1 |
2 |
byte offset to the first freeblock |
3 |
2 |
number of cells on this page |
5 |
2 |
first byte of the cell content area |
7 |
1 |
number of fragmented free bytes |
8 |
4 |
Right child (the Ptr(N) value). Omitted on leaves. |
Flags可以表示四種類型的Btree頁,其值如下:
Ø 如果是B+tree的葉子頁,該位元組值為0X0D,
Ø 如果是B+tree的內部頁,該位元組值為0X05,
Ø 如果是B-tree的葉子頁,該位元組值為0X0A,
Ø 如果是B-tree的內部頁,該位元組值為0X02。
而這四種Btree頁的單元格式如表2.4所示。
表2.4 Btree頁單元格式
資料型別 |
Appears in... |
說明 |
|||
Table Leaf (0x0d) |
Table Interior (0x05) |
Index Leaf (0x0a) |
Index Interior (0x02) |
||
4-byte integer |
✔ |
✔ |
Page number of left child |
||
varint |
✔ |
✔ |
✔ |
Number of bytes of payload |
|
varint |
✔ |
✔ |
Rowid |
||
byte array |
✔ |
✔ |
✔ |
Payload |
|
4-byte integer |
✔ |
✔ |
✔ |
Page number of first overflow page |
2.5.1.6 Cell Payload OverflowPages
當btree頁的一個btree單元的payload太大時,超出的部分機會放入溢位頁。這些溢位頁會形成一個連結串列。每個溢位頁的前四個位元組都是大端整數,指的是在連結串列上下一頁的頁碼,不過如果為零的話就是指它是最後一頁。之後的有效位元組都是用來儲存溢位內容。其結構如圖2.7所示。
圖2.7 溢位頁
2.5.1.7 Pointer Map or PtrmapPages
Pointer map或者ptrmap頁都是額外插入資料庫用來使auto_vacuum和incremental_vacuum操作更加高效的附加頁。
資料庫中其他的頁都是從從雙親節點指向孩子節點,而ptrmap頁正好相反。