InnoDB從內分析之區和段(三)

so_easy發表於2020-09-19

前言

還是先看這個圖:
InnoDB從內分析(一)

Segment(段)

段的概念:段是概念上區的分類集合。

Extent(區)

區的概念:由連續的64個16KB的Page組成的大小為1M的空間。區是實際的1M物理空間。當碎片區用完之後,就會申請1M的連續物理空間。

Fragment(碎片區)

新建的表空間並不會直接申請1M的區儲存,實際只有96KB,即6頁。從下文的結構分析知道FragmentINODE頁管理,如果32個Fragment都用完之後才會申請完整的1MExtent空間。

表空間的結構圖

不論是系統表空間還是獨立表空間,都可以看成是由若干個區組成的,每256個區被劃分成一組。
InnoDB從內分析之區和段(三)
其中第一個組的extent0的前三個頁面資料結構固定是:

- FSP_HDR:表空間頭
- IBUF_BITMAP- INODE:段的資料結構

其他的extent的前兩個頁面是固定的:

- XDES:這組區的描述
- IBUF_BITMAP

InnoDB從內分析之區和段(三)

Extent除了這幾個特殊的Page之外,其他就是前文講過的Page–用來儲存資料的頁。之所以這裡有這幾個特殊的頁是為了對這個表空間能夠進行有效的管理。而如何管理則是下文的目的了。透過學習這些資料的儲存,可以瞭解到InnoDB的儲存結構以及對今後如何最佳化InnoDB查詢有更深的瞭解。

FSP_HDR的結構示意圖

InnoDB從內分析之區和段(三)

- `FILE Header`:檔案頭
- `File Space Header`:表空間的頭資訊
- `XDES Entry`:區描述例項。256個區別劃分成了一組,組內的每一個區都對應一個`XDES Entry`- `Empty Space`:空閒區域
- `File Traile`:檔案尾部

FIle Space Header結構示意圖

InnoDB從內分析之區和段(三)

下文的List Base Node佔用16B,指向的Extent的首節點和尾節點。具體的意義如下圖:

InnoDB從內分析之區和段(三)

- `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
資料段:單獨的區的集合存放葉子節點
索引段:單獨的區的集合存放非葉子節點

InnoDB從內分析之區和段(三)

- `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結構示意圖

InnoDB從內分析之區和段(三)

- `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)是對區的描述實體,用來記錄區的資訊。
InnoDB從內分析之區和段(三)

- 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個頁面是哪些頁面的組成呢?舉個例子:
InnoDB從內分析之區和段(三)

  • 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、7File Segment inodepage offset 2。這次的重點分析物件是INDOEINode Entry,求證一個索引分兩段的儲存關係是如何儲存的。

第二步:開始分析page offset 2-INDOE

INODE二進位制分析
求證得到的結論就是:

  • 一個索引分兩段,最開始的時候就以INode Entry結構儲存了段的例項。
  • 這個例子的資料表是有兩個索引,所以一共用了4個INode Entry。但是INode Entry又是如何管理的Extent呢?
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章