前言
還是先看這個圖:
Segment(段)
段的概念:段是概念上區的分類集合。
Extent(區)
區的概念:由連續的64個16KB的Page組成的大小為1M的空間
。區是實際的1M物理空間。當碎片區
用完之後,就會申請1M的連續物理空間。
Fragment(碎片區)
新建的表空間並不會直接申請1M的區儲存,實際只有96KB,即6頁。從下文的結構分析知道Fragment
由INODE
頁管理,如果32個Fragment
都用完之後才會申請完整的1MExtent
空間。
表空間的結構圖
不論是系統表空間還是獨立表空間,都可以看成是由若干個區組成的,每256個區被劃分成一組。
其中第一個組的extent0
的前三個頁面資料結構固定是:
- FSP_HDR:表空間頭
- IBUF_BITMAP:
- INODE:段的資料結構
其他的extent
的前兩個頁面是固定的:
- XDES:這組區的描述
- IBUF_BITMAP:
Extent
除了這幾個特殊的Page
之外,其他就是前文講過的Page
–用來儲存資料的頁。之所以這裡有這幾個特殊的頁是為了對這個表空間
能夠進行有效的管理。而如何管理則是下文的目的了。透過學習這些資料的儲存,可以瞭解到InnoDB
的儲存結構以及對今後如何最佳化InnoDB查詢有更深的瞭解。
FSP_HDR的結構示意圖
- `FILE Header`:檔案頭
- `File Space Header`:表空間的頭資訊
- `XDES Entry`:區描述例項。256個區別劃分成了一組,組內的每一個區都對應一個`XDES Entry`。
- `Empty Space`:空閒區域
- `File Traile`:檔案尾部
FIle Space Header結構示意圖
下文的
List Base Node
佔用16B,指向的Extent
的首節點和尾節點。具體的意義如下圖:
- `Space ID`:表空間ID
- `Not Used`:保留欄位,未使用
- `Size`:當前表空間總的`Page`個數
- `FREE Limit`:當前尚未初始化的最小`Page No`。從該`Page`往後的都尚未加入到`表空間`的`FREE LIST`上
- `Space Flags`:
- `FRAG_N_USED`:`FREE_FRAG`連結串列上已被使用的`Page`數,用於快速計算該連結串列可用空閒`Page`數
- `List Base Node For FREE List`(簡寫`FSP_FREE`):當一個`Extent`中的所有`Page`都未被使用,放到該連結串列上,用於隨後的分配。
- `List Base Node For FREE_FRAG List`(簡寫`FSP_FREE_FRAG`):`FREE_FRAG`連結串列的基點,通常這樣的`Extent`中的`Page`可能歸屬於不同的`segment`,用於`segment frag array page`的分配
- `List Base Node For FULL_FRAG List`(簡寫`FSP_FULL_FRAG`):`Extent`中所有的`Page`都被使用時,放到該連結串列上。當有`Page`從該`Extent`釋放時,則移回`FREE_FRAG`連結串列
- `Next Unused Segment ID`:當前檔案中最大的段ID+1,用於段分配時的seg id計數器
- `List Base Node for SEG_INODES_FULL`:已被完全用滿的`Inode page`連結串列
- `List Base Node for SEG_INODES_FREE`:至少存在一個空閒的`Inode Entry`的`Inode Page`被放到該連結串列
INDOE結構示意圖
INODE
資料段:單獨的區的集合存放葉子節點
索引段:單獨的區的集合存放非葉子節點
- `FILE Header`:檔案頭
- `List Node for Node Page List` :`INODE`雙向連結串列
Prev Node Page Number
Prev Node Offsct
Next Node Page Number
Next Node Offset
- INODE Entry:段實體
- Empty Space:空閒空間
- File Tailer:檔案尾
INODE Entry結構示意圖
- `Segment ID`:段ID
- `NOT_FULL_N_USED`:在`NOT_FULL`連結串列中已經使用了多少個頁面
- `List Base Node For FREE List`:完全沒有被使用並分配給該`Segment`的`Extent`連結串列。
- `List Base Node For NOT_FULL List`:至少有一個`Page`分配給當前`Segment`的`Extent`連結串列,全部用完時,轉移到`FSEG_FULL`,全部釋放時,則歸還`FSP_FREE`。
- `List Base Node For FULL List`:分配給當前`Segment`且`Page`完全使用完的`Extent`連結串列。
- `Magic Number`:標記這個`INODE Entry`是否已經被初始化,初始化的意思就是把各個欄位的值都填進去了。
- `Fragment Array Entry`:指向某個`Page`的指標,記錄的是相對位置。
XDES結構示意圖
XDES Entry
(Extent Descriptor Entry)是對區的描述實體,用來記錄區的資訊。
- SegmentID:段ID。每個表空間分為`葉子節點段`和`非葉子結點段`,也可稱為`資料段`和`索引段`。每個段都有自己的ID,這個值透過`INODE`結構儲存。
- List Node:指向前後`XDES Entry`的指標。
`Prev Node Page Number`:前一個節點的頁號
`Prev Node Offset`:前一個節點的相對位置
`Next Node Page Number`:下一個節點的頁號
`Next Node Offset`:下一個節點的相對位置
- State:區的狀態。
FREE:直屬於表空間-空閒區。
FREE_FRAG:直屬於表空間-有剩餘空間的碎片區。
FULL_FRAG:直屬於表空間-沒有剩餘空間的碎片區。
FSEG:附屬於某個段的區
- Page State Bitmap:1M的連續空間分配給了64個`Page`。16B=128bit,每2個bit表示1個`Page`的狀態。`00`表示空閒,`10`表示已寫。
資料結構分析
從每個結構來看似乎還是很清晰的,就猶如表空間
的資料結構圖把自己的每一個部分的細微結構都展現了出來。但是這些結構是如何組合起來工作的,卻一下子摸不著頭腦。接下來就慢慢的分析這些資料結構之間的關係。
首先:聊一聊碎片區
前面提到過一個表剛被建立的時候只有96KB,也即6個頁面。其中這6個頁面是哪些頁面的組成呢?舉個例子:
File Space Header
:表空間頭Insert Buffer Bitmap
:表空間的IBUF_BITMAP
B-tree Node
:這是一個Page
,表示的是葉子節點
File Segment inode
:表空間的INODE
頁面。Freshly Allocated Page
:剩下2個Page
還未被使用。
(1)剛建立的表不會分配完整的Extent
,表中的資料開始都是儲存在Fragment
碎片區。表空間
除了固定的3個Page
之外,剩下的就是3個空閒的Page
。
(2)這3個空閒的Page
一開始建表的時候是從File Space Header
.FSP_FREE_FRAG
連結串列指向的碎片區
(還有空閒Page的Extent)分配的。
(3)如果3個空閒Page
被寫滿,這些頁面的頁號會被依次寫在INODE
.INode Entry
.Fragment Array Entry:0-2
,並且向FSP_FREE_FRAG
指向的的碎片區
申請新的Page
,這個新的Page
的頁號被寫在Fragment Array Entry 3
。—如此下去就會把INODE
.INode Entry
的32個Fragment
用完,接下來就是分配完整的Extent
。如果碎片區
的空閒頁面被分配完畢,則這個碎片區
就會被連結到FSP_FULL_FRAG
。反之如果FSP_FULL_FRAG
有空閒的頁面則又會被連結回FSP_FREE_FRAG
。
(4)FSP_FREE_FRAG
的碎片區
最開始是從FSP_FREE
中申請的。
(5)碎片區
用完之後,開始申請完整的1MExtent
。這個Extent
也是從FSP_FREE
申請。每一個Extent
都對應了一個FSP_HDR
.XDES Entry
。256個Extent
被視為一組,每個Extent
都有唯一對應的XDES Entry
。所以透過FSP_HDR
就可以管理這一組的Extent
。
再而:聊一聊段
一個索引有兩個段:資料段
和索引段
。資料段
指的就是單獨存放葉子節點
的區連結串列,而索引段
指的就是單獨存放非葉子結點
的區連結串列。該如何去理解呢?下面透過實踐分析某個資料表的二進位制儲存檔案。
第一步:透過py_innodb_page_info.py檢查ibd
bash-3.2# python py_innodb_page_info.py ../lottery_match/league_map.ibd -v
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0001>
page offset 00000004, page type <B-tree Node>, page level <0000>
page offset 00000005, page type <B-tree Node>, page level <0000>
page offset 00000006, page type <B-tree Node>, page level <0000>
page offset 00000007, page type <B-tree Node>, page level <0000>
page offset 00000000, page type <Freshly Allocated Page>
Total number of page: 9:
Freshly Allocated Page: 1
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 5
File Segment inode: 1
如上存在葉子節點
和非葉子節點
。葉子節點
在page offset 3
,非葉子節點在
在page offset 4、5、6、7
。File Segment inode
在page offset 2
。這次的重點分析物件是INDOE
的INode Entry
,求證一個索引分兩段的儲存關係是如何儲存的。
第二步:開始分析page offset 2
-INDOE
見INODE二進位制分析
求證得到的結論就是:
- 一個索引分兩段,最開始的時候就以
INode Entry
結構儲存了段的例項。 - 這個例子的資料表是有兩個索引,所以一共用了4個
INode Entry
。但是INode Entry
又是如何管理的Extent
呢?
本作品採用《CC 協議》,轉載必須註明作者和本文連結