頁
主數(shù)據(jù)庫文件頭之后的都是頁的內(nèi)容了扬跋,頁的分類主要有5種:b-tree
頁脆栋,overflow
頁,free
頁鸠匀,lock-byte
頁以及pointer map page
蕉斜。
主要使用到的是前三者,lock-byte
頁只要是為了支持某些文件系統(tǒng)使用的是強制性加文件鎖缀棍,而pointer map page
是為了支持 auto_vacuum
和incremental_vacuum
模式宅此,這兩個以后再說。
頁的格式
空閑頁 free page
空閑頁分為chunk
和 leaf
爬范,前者存放著多個leaf
空閑頁(里面沒有任何數(shù)據(jù))父腕,并且chunk
會串起來成為一個freelist
,官網(wǎng)是這么說的:The freelist is organized as a linked list of freelist trunk pages with each trunk page containing page numbers for zero or more freelist leaf pages.
Offset | Size | Description |
---|---|---|
0 | 4 | 下一個freelist chunk page 的頁號 |
4 | 4 | L=當(dāng)前chunk 存放的freelist leaf page 的數(shù)目 |
8 | L*4 |
freelist leaf page 頁號組成的數(shù)組青瀑,長度等于L |
b-tree頁 B-tree page
關(guān)于b樹和b+樹的璧亮,參考博客:B樹與B+樹
b-tree頁格式
- The 100-byte database file header (found on page 1 only)
- The 8 or 12 byte b-tree page header
- The cell pointer array
- Unallocated space
- The cell content area
- The reserved region.
主數(shù)據(jù)庫文件頭只在頁1有,保留區(qū)間默認(rèn)也是沒有的斥难,所以剩下的就是
頁頭 |
---|
cell 指針數(shù)組 |
未使用區(qū)間 |
cell 內(nèi)容區(qū) |
先不著急看頁頭和cell的具體定義枝嘶,回到我們那個小文件分析中,還是利用剛才的showdb工具依次輸入./showdb small.db NbdCCC
哑诊,N為頁號(例子中N=1~3):
PS G:\code-2\sqlite3> ./showdb small.db 1bdCCC
Pagesize: 4096
Available pages: 1..2
Header on btree page 1:
000: 0d 13 table leaf
001: 00 00 0 Offset to first freeblock
003: 00 01 1 Number of cells on this page
005: 0f a3 4003 Offset to cell content area
007: 00 0 Fragmented byte count
Cell[0]:
f3f: 5b payload-size: 91
f40: 01 rowid: 1
f41: 07 record-header-size: 7
f42: 17 typecode[0]: 23 - text(5)
f43: 21 typecode[1]: 33 - text(10)
f44: 21 typecode[2]: 33 - text(10)
f45: 01 typecode[3]: 1 - int8
f46: 81 01 typecode[4]: 129 - text(58)
f48: 74 61 62 6c 65 data[0]: 'table'
f4d: 64 65 70 61 72 74 6d 65 6e data[1]: 'department'
f57: 64 65 70 61 72 74 6d 65 6e data[2]: 'department'
f61: 02 data[3]: 2
f62: 43 52 45 41 54 45 20 54 41 data[4]: 'CREATE TABLE department...'
這里涉及提前涉及到了sqlite_schema
的概念群扶,它存放著其他所有表、索引、觸發(fā)器對應(yīng)的根b-tree頁號竞阐,而這由一張?zhí)摂M的表組成缴饭,并且sqlite3默認(rèn)該表的根b-tree頁號為1。sqlite_schema
的定義如下
CREATE TABLE sqlite_schema(
type text,
name text,
tbl_name text,
rootpage integer,
sql text
);
暫時了解到這里即可馁菜,回頭看頁1茴扁,可以看到Cell[0]存放著的正是department
這張表的信息,從里面我預(yù)先(可以先講格式汪疮,但這樣就很無聊了)知道了rootpage = 2(cell[0]里面的data[3]==2)峭火。
故而接著解析頁2,這時候我們就可以看到我們之前插入的兩條數(shù)據(jù):
PS G:\code-2\sqlite3> ./showdb small.db 2bdCCC
Pagesize: 4096
Available pages: 1..2
Header on btree page 2:
000: 0d 13 table leaf
001: 00 00 0 Offset to first freeblock
003: 00 02 2 Number of cells on this page
005: 0f ea 4074 Offset to cell content area
007: 00 0 Fragmented byte count
Cell[0]:
ff5: 09 payload-size: 9
ff6: 01 rowid: 1
ff7: 04 record-header-size: 4
ff8: 09 typecode[0]: 9 - one
ff9: 15 typecode[1]: 21 - text(4)
ffa: 01 typecode[2]: 1 - int8
ffb: 74 65 73 74 data[1]: 'test'
fff: ff data[2]: -1
Cell[1]:
fea: 09 payload-size: 9
feb: 02 rowid: 2
fec: 04 record-header-size: 4
fed: 01 typecode[0]: 1 - int8
fee: 15 typecode[1]: 21 - text(4)
fef: 09 typecode[2]: 9 - one
ff0: 02 data[0]: 2
ff1: 74 65 73 74 data[1]: 'test'
之前插入的兩條數(shù)據(jù)
sqlite> insert into department values(1, "test", -1);
sqlite> insert into department values(2, "test", 1);
結(jié)合上述數(shù)據(jù)再來分析頁頭以及別的數(shù)據(jù)格式的內(nèi)容智嚷。
b-tree 頁頭內(nèi)容
- 偏移0x0 存放著的是b-tree頁的類型卖丸,總共有4種
- 0x02 是 內(nèi)部索引 b-tree頁,interior index b-tree page
- 0x05 是 內(nèi)部表 b-tree頁盏道,interior table b-tree page
- 0x0a 是 葉節(jié)點索引 b-tree頁稍浆,leaf index b-tree page
- 0x0d 是 葉節(jié)點表 b-tree頁,leaf table b-tree page
- 偏移0x1和0x7猜嘱,是與cell被刪除的時候留下的磁盤碎片問題衅枫,大于等于4字節(jié)的磁盤碎片被稱為freeblock,串成一條鏈表朗伶,由page header存儲首個freeblock弦撩;小于4字節(jié)的被稱為fragment,并且會被累加到Fragmented byte count里面论皆。
- 偏移0x03和0x05與cell有關(guān)益楼,前者存儲cell的個數(shù),后者存放cell內(nèi)容的起始偏移
- 還有0x08處点晴,如果是 interior table b-tree page的話感凤,還會存放一個右子樹的頁號。
- 剩下的就是cell指針數(shù)組了粒督。
那么問題來了陪竿,cell究竟是啥?cell我的理解就是上述四種節(jié)點的類型的具體存放內(nèi)容坠陈。來自官網(wǎng)的介紹
Datatype | Table Leaf (0x0d) | Table Interior (0x05) | Index Leaf (0x0a) | Index Interior (0x02) | Description |
---|---|---|---|---|---|
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 |
這里剩下的問題就是playload里面存放著什么了萨惑。不管是table還是index都是 "record format",也就是記錄列的個數(shù)仇矾,列的類型庸蔼,以及每一列的內(nèi)容。
對于Table Leaf
來說贮匕,里面存放的就是一行數(shù)據(jù)姐仅,其格式如下:
record-header-size |
---|
record-header |
data array |
- record-header-size:表明該記錄頭部+列的類型的大小。用的是可變長整型
- record-header: 記錄每一列的類型
serial type
,也是一個可變長整型掏膏。與此同時它也承擔(dān)著該列內(nèi)容的長度劳翰。 - data:則是每一列的內(nèi)容,整數(shù)由
serial type
定義長度馒疹,而字符串和二進(jìn)制數(shù)據(jù)長度由serial type
給出佳簸,其余的值直接由serial type
給出:
以下是serial type
的定義:
Serial Type | Content Size | Meaning |
---|---|---|
0 | 0 | Value is a NULL. |
1 | 1 | Value is an 8-bit twos-complement integer. |
2 | 2 | Value is a big-endian 16-bit twos-complement integer. |
3 | 3 | Value is a big-endian 24-bit twos-complement integer. |
4 | 4 | Value is a big-endian 32-bit twos-complement integer. |
5 | 6 | Value is a big-endian 48-bit twos-complement integer. |
6 | 8 | Value is a big-endian 64-bit twos-complement integer. |
7 | 8 | Value is a big-endian IEEE 754-2008 64-bit floating point number. |
8 | 0 | Value is the integer 0. (Only available for schema format 4 and higher.) |
9 | 0 | Value is the integer 1. (Only available for schema format 4 and higher.) |
10,11 | variable | Reserved for internal use. These serial type codes will never appear in a well-formed database file, but they might be used in transient and temporary database files that SQLite sometimes generates for its own use. The meanings of these codes can shift from one release of SQLite to the next. |
N≥12 and even | (N-12)/2 | Value is a BLOB that is (N-12)/2 bytes in length. |
N≥13 and odd | (N-13)/2 | Value |
舉個例子:
Cell[0]:
ff5: 09 payload-size: 9
ff6: 01 rowid: 1
ff7: 04 record-header-size: 4
ff8: 09 typecode[0]: 9 - one
ff9: 15 typecode[1]: 21 - text(4)
ffa: 01 typecode[2]: 1 - int8
ffb: 74 65 73 74 data[1]: 'test'
fff: ff data[2]: -1
該cell對應(yīng)著:insert into department values(1, "test", -1);
數(shù)據(jù)依次是:整數(shù)1,字符串"test"颖变,整數(shù)-1
整型生均,字符串,整型腥刹,這信息對應(yīng)著0xff8马胧,0xff9,0xffa衔峰,
其中字符串的長度 = (21 - 13)/2 = 4佩脊。那如果是奇數(shù)長度的字符串呢?
因為schema format
是4垫卤,故1可以直接用 09來表示威彰。
……
剩下的就不說了。
不過有一點值得注意的是穴肘,這插入其實是逆序執(zhí)行的抱冷,回憶一下page的分布,cell content是從下往上伸展梢褐,而cell pointer array是從上往下伸展。故越新的數(shù)據(jù)赵讯,在越上面盈咳。這是為了減少頁的磁盤碎片化而使用的機制。
至此最簡單的一個數(shù)據(jù)庫文件已經(jīng)分析完边翼。但是還留下以下問題:
- 1鱼响、overflow page 是怎么一回事?用在什么場景组底?
- 2丈积、index 和 table 的
interior
類型還沒有討論? - 3债鸡、vacuum 是什么一回事江滨?
- 4、wal-mode 和 hot-journal mode 具體的意義厌均?