前言
還是先看這個圖:
Segment(段)
段的概念:段是概念上區的分類集合。
Extent(區)
區的概念:由連續的64個16KB的Page組成的大小為1M的空間
稱之為區。區是實際的1M物理空間。當一個區用完之後,表空間會申請1M的連續物理空間分配給區。
為什麼要有Extent
(區)的概念呢?
雖然頁與頁之間可以通過雙向連結串列連線,但是頁的物理儲存是隨機IO
。通過指標去尋找隨機IO
可能會浪費大量的時間。熟悉C的話必然知道連結串列與陣列的區別其實就是儲存的方式不同。再而聯想到Redis
的raw
和embstr
的儲存區別。因而就很容易發現分配區
的好處了–連續的頁在定址的時候速度更加的快。
為什麼要有Fragment
(碎片區)的概念呢?
新建立一張表的時候,MySQL並不會為這個新建立的表分配1M的儲存空間,實際上是隻分配了96KB
的大小儲存空間–即6頁。之所以不分配1M的空間是為了防止空間浪費,只有當Fragment
寫完了才會去申請完整的1MExtent
空間。
表空間的結構圖
不論是系統表空間還是獨立表空間,都可以看成是由若干個區組成的,每256個區被劃分成一組。
其中第一個組的extent0
的前三個頁面資料結構固定是:
- FSP_HDR:表空間頭
- IBUF_BITMAP:
- INODE:段的資料結構
其他的extent
的前兩個頁面是固定的:
- XDES:區的描述例項
- IBUF_BITMAP:
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:檔案尾
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`表示空閒,`01`表示已寫。
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`的指標,記錄的是相對位置。
FSP_HDR的結構示意圖
- `FILE Header`:檔案頭
- `File Space Header`:表空間的頭資訊
- `XDES Entry`:區描述例項。256個區別劃分成了一組,組內的每一個區都對應一個`XDES Entry`。
- `Empty Space`:空閒區域
- `File Traile`:檔案尾部
FIle Space Header結構示意圖
- `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`被放到該連結串列
資料結構分析
都是自己總結琢磨出來的,所以有錯誤的地方希望指正,非常感謝!
從每個結構來看似乎還是很清晰的,就猶如表空間
的資料結構圖把自己的每一個部分的細微結構都展現了出來。但是這些結構是如何組合起來工作的,卻一下子摸不著頭腦。接下來就慢慢的分析這些資料結構之間的關係。
首先:聊一聊碎片區
前面提到過一個表剛被建立的時候只有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
。這3個空閒的Page
一開始建表的時候是申請的File Space Header
.FSP_FREE_FRAG
連結串列指向的碎片區
(還有空閒Page的Extent)。如果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
。
(2)FSP_FREE_FRAG
的碎片區
最開始是從FSP_FREE
中申請的。
(3)碎片區
用完之後,開始申請完整的1MExtent
。這個Extent
也是從FSP_FREE
申請而得。每一個Extent
都對應了一個XDES Entry
,所以這個XDES Entry
就會記錄當前的段ID,並且通過XDES Entry
.State
記錄當前的狀態。通過XDES Entry
.Page State Bitmap
記錄該Extent
每個Page
的使用情況。
(4)256個Extent
被視為一組,每個Extent
都有唯一對應的XDES Entry
,新申請的FSP_FREE
的Extent
也不例外。只是這個時候它的state
是FREE
,所有的Extent
申請都是來自於它。如上第(3)種情況申請的Extent
,它的state
是FSEG
。Extent
處於碎片區
並且在FSP_FREE_FRAG
連結串列下則state
是FREE_FRAG
。Extent
處於碎片區
並且在FSP_FULL_FRAG
連結串列下則state
是FULL_FRAG
。
經過上面的講解,我當是已經理解了
碎片區
、FSP_FREE_FRAG
、FSP_FULL_FRAG
、Fragment Array Entry
概念了。
再而:聊一聊段
一個索引有兩個段:資料段
和索引段
。資料段
指的就是單獨存放葉子節點
的區連結串列,而索引段
指的就是單獨存放非葉子結點
的區連結串列。該如何去理解呢?
如上的概述也不知道在哪裡看到的了,而且也得不到確切的真實資料作為補充論證。如果可以像之前講
Row
和Page
似的可以分析,那真是再好不過了。竟然已經學瞭如何分析,那就學以致用吧,一起去看看如何分析和求證。
第一步:通過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
結構儲存了段的例項。
可是:單獨存放葉子節點
的區連結串列,和單獨存放非葉子節點
的區連結串列又該如何解釋和驗證呢?
第三步:分析資料大於1M的ibd檔案
本作品採用《CC 協議》,轉載必須註明作者和本文連結