Innodb表空間、段、區描述頁分析與磁盤存盤空間管理
從一個整體方向結構上看,表空間大的結構圖如下

-
表空間:表空間檔案,存放資料庫資料的載體,對于系統表空間通常是ibdata1,開啟獨立表空間檔案
innodb_file_per_table=1后,對應的表空間為.ibd后綴的表空間檔案 -
資料段(segment):邏輯上的概念,與資料庫中的索引相映射,資料表由多個段(索引)組成,段的型別有資料段(葉子結點)、索引段(非葉節點),一個索引占據兩個段,分別對應葉節點和非葉節點,默認地,對于一張只有主鍵的表,會默認存在兩個段,分別對應主索引的非葉節點和葉節點段
-
區(extent):
- 對于小表,可以忽略區的概念,表基本由段以及離散的資料page頁構成,
- 隨著表空間資料量的增長,系統對于磁盤空間的分配按區(extent)來進行,extent代表一組連續的page,默認64個page(1M大小),extent的作用是提高page分配效率,通常來講批量分配從效率上總是優于單一的page分配,另外對于資料連續性方面也更優
-
頁:mysql存盤的基礎單元,一個頁的大小通常是16k,innodb將xxx.ibd(表空間檔案)按page切分
實際上的ibd檔案構成

資料在物理檔案上按16k被切分成一個一個的資料頁,每個頁有自己的頁編號(pageNo)
每一個表空間檔案前面三個頁通常稱之為管理頁(元資料頁)
段與區的內容比較負責,主要是負責磁盤空間的管理、連續空間申請,介紹段、區的詳細內容之前,我們先來看一下表空間檔案的元資料頁資訊
元資料頁概述
-
表空間檔案的第一個16kb,pageno=0的資料頁稱之為File Space Header,主要有兩部分內容構成,fsp_header/xdes_entry
- fsp_header記錄整個表空間具體資訊
- xdes_entry記錄當前表空間連續的256個extent資訊,每間隔256M空間就會插入一個xdes_entry,不同的是后續的xdes_entry頁面不包含fsp_header
-
表空間檔案的第二個16kb,pageno=1的資料頁稱之為Insert Buffer Bitmap,插入緩沖位圖頁,記錄后續連續16384個資料頁(256M)的插入緩沖位圖資訊,可以看到每隔256M的表空間間距均會新增兩個資料頁,一個是xdes_entry描述頁,一個是Insert Buffer Bitmap描述頁
-
表空間的檔案的第三個16kb,pageno=2的資料頁記錄的是File Segment inode段資訊記錄頁,也稱之為inode資訊頁,一個inode資訊頁記錄85個segment段資訊,一個索引占據兩個段描述符號,通常情況下對于獨立表空間檔案,此頁面通常只需要一個即可滿足大部分需求,感興趣的可以試試如下的資料表
CREATE TABLE `test_fsp` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL DEFAULT '0',
`b` int(11) NOT NULL DEFAULT '0',
`c` int(11) NOT NULL DEFAULT '0',
`d` int(11) NOT NULL DEFAULT '0',
`e` int(11) NOT NULL DEFAULT '0',
`f` int(11) NOT NULL DEFAULT '0',
`g` int(11) NOT NULL DEFAULT '0',
`h` int(11) NOT NULL DEFAULT '0',
`i` int(11) NOT NULL DEFAULT '0',
`j` int(11) NOT NULL DEFAULT '0',
`k` int(11) NOT NULL DEFAULT '0',
`l` int(11) NOT NULL DEFAULT '0',
`m` int(11) NOT NULL DEFAULT '0',
`n` int(11) NOT NULL DEFAULT '0',
`o` int(11) NOT NULL DEFAULT '0',
`p` int(11) NOT NULL DEFAULT '0',
`q` int(11) NOT NULL DEFAULT '0',
`r` int(11) NOT NULL DEFAULT '0',
`s` int(11) NOT NULL DEFAULT '0',
`t` int(11) NOT NULL DEFAULT '0',
`u` int(11) NOT NULL DEFAULT '0',
`v` int(11) NOT NULL DEFAULT '0',
`w` int(11) NOT NULL DEFAULT '0',
`x` int(11) NOT NULL DEFAULT '0',
`y` int(11) NOT NULL DEFAULT '0',
`z` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `ix_a` (`a`),
KEY `ix_b` (`b`),
KEY `ix_c` (`c`),
KEY `ix_d` (`d`),
KEY `ix_e` (`e`),
KEY `ix_f` (`f`),
KEY `ix_g` (`g`),
KEY `ix_h` (`h`),
KEY `ix_i` (`i`),
KEY `ix_j` (`j`),
KEY `ix_k` (`k`),
KEY `ix_l` (`l`),
KEY `ix_m` (`m`),
KEY `ix_n` (`n`),
KEY `ix_o` (`o`),
KEY `ix_p` (`p`),
KEY `ix_q` (`q`),
KEY `ix_r` (`r`),
KEY `ix_s` (`s`),
KEY `ix_t` (`t`),
KEY `ix_u` (`u`),
KEY `ix_v` (`v`),
KEY `ix_w` (`w`),
KEY `ix_x` (`x`),
KEY `ix_y` (`y`),
KEY `ix_z` (`z`),
KEY `ix_ab` (`a`,`b`),
KEY `ix_cd` (`c`,`d`),
KEY `ix_ef` (`e`,`f`),
KEY `ix_gh` (`g`,`h`),
KEY `ix_ij` (`i`,`j`),
KEY `ix_kl` (`k`,`l`),
KEY `ix_mn` (`m`,`n`),
KEY `ix_op` (`o`,`p`),
KEY `ix_qr` (`q`,`r`),
KEY `ix_st` (`s`,`t`),
KEY `ix_uv` (`u`,`v`),
KEY `ix_wx` (`w`,`x`),
KEY `ix_yz` (`y`,`z`),
KEY `ix_abc` (`a`,`b`,`c`),
KEY `ix_edf` (`e`,`d`,`f`),
KEY `ix_ghi` (`g`,`h`,`i`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
此表包含26個英文字母資料列+id主鍵列,索引數量1+26+13+3 = 43,總共需要的段的資料量數目是43*2=86,超過了85個,此時在執行mysql_page_info test/test_fsp.ibd 可以看到輸出File Segment inode: 2, 變成2了,亦即此時需要兩個inode資料頁才能滿足需求,不過一般情形下一張表的索引數不會這么多
FSP_HDR/XDES_ENTRY
整體結構如下:

可以看到在除了page0頁之外的xdes描述頁,fsp header均為空,xdes_entry中記錄256個entry,每個entry記錄一個區的資訊
fsp header結構如下:

- Space Id(4)/FSP_SPACE_ID 記錄當前表空間ID
- (Unused) (4) 暫未使用的空間
- Highest page number in file (size) (4) / FSP_SIZE 代表當前表空間資料頁的數量計算方式為(ibd_file_size*1024/16)
- Highest page number initialized (free limit) (4) /FSP_FREE_LIMIT 當前表空間檔案中未被初始化的資料頁起始位置,通常當表空間資料頁不足的時候,需要從此位置開始申請資料頁
- Flags (4) /FSP_SPACE_FLAGS 標志位
- Number of pages used in "FREE_FRAG" list (4) /FSP_FRAG_N_USED FREE_FRAG 空閑鏈表上的資料頁數量
- List base node for "FREE" list (16) / FSP_FREE 所有的Page均為空閑的Extent鏈表
- List base node for "FREE_FRAG" list (16) / FSP_FREE_FRAG Extend中的page部分被使用的鏈表
- List base node for "FULL_FRAG" list (16) / FSP_FULL_FRAG Extend中的page全部被使用的鏈表
- Next Unused Segment ID (8) /FSP_SEG_ID 表示下一個未被使用的Segment ID
- List base node for "FULL_INODES" list (16)/FSP_SEG_INODES_FULL 表示Segment Page的所有的Inode均已被使用
- List base node for "FREE_INODES" list (16)/FSP_SEG_INODES_FREE 表示Segment Page存在空閑的Inode
檔案鏈表(List base node/List Node):

-
包含一個描述鏈表長度的4位元組
-
兩個6位元組的"First"/"Last"指標構成的雙向兩表,通常這部分用于表頭節點,記錄整個鏈表的起始與結束位置

- 包含"Prev"/"Next"指標構成雙向鏈表的節點,這部分通常用于構成鏈表的節點部分(比如Xdes_entry中的資料結構),,鏈表由pageNo(4)+offset(2)構成,prev(first)/next(last)一共12位元組(頁的大小為16KB,所以2位元組(16bit)的頁內偏移即可)
First、Prev/Last、Next的大小均為6位元組的大小,fsp_header中主要記錄段(indodes)、區/頁(frag) 的分配鏈表資訊以及整個表空間整體的資料頁數量、segment數量等元素資訊
表空間的檔案分配規則大致如下所述
-
對于新表,依據建表陳述句索引個數多少,此階段對于系統存盤空間按照碎片頁的方式分配,亦即此時存盤空間的申請基本是16kb*N的的方式遞增
-
隨著資料的插入,當資料量逐漸增多,資料容量逐漸超過32個碎片頁的大小時候(通常比這個會小點,系統申請空間通常會預留一部分空閑空間,此處舉例說明分配流程)新的磁磁盤空間按區申請(1M)
- 此處的32跟segment的結構有關系,后續會說明
-
隨著更多的資料插入,系統按照1M的方式逐漸擴張表空間檔案,當表空間的容量達到32M,系統會以4M(4個區大小)容量的方式擴充表空間的容量大小
前面提到過,對于表空間檔案,需要盡量滿足資料頁在磁盤存盤上的連續性,通常情況下批量分配能夠保證一組資料頁的連續性,在平衡小表空間使用率的情況下,上述分配方式能夠盡量的減小磁盤空間的浪費,同時對于大表也能保證后續分配資料頁在整體上都是連續的
page0的第二部分內容即位xdes_entry,每個xdes_entry大小為40位元組,一共256個xdes_entry,記錄接下來的256個區(256M表空間)的資訊,xdes_entry的結構如下:

- File Segment ID (8) / FSEG_ID 當前的區所屬的段號,0代表當前區內的頁面通常分屬于不同的段,亦即當前區里面的頁大概率是碎片頁,具體還需要參見state欄位
- List node for XDES list (12) 區塊的檔案雙向鏈表
- State (4) 當前區的狀態
- FREE:歸屬于 FSP_FREE 鏈表(參見FSP_HEADER小結的FSP_FREE)
- FREE_FRAG:歸屬于 FSP_FREE_FRAG 鏈表 (參見FSP_HEADER小結的FSP_FREE_FRAG)
- FULL_FRAG:歸屬于 FSP_FULL_FRAG 鏈表 (參見FSP_HEADER小結的FSP_FULL_FRAG)
- FSEG:歸屬于某個Segment,亦即此時上述FSEG_ID在state為此值時候值才會大于0(有意義的segment id)
- Page State Bitmap (16) 2 bits per page, 1=free, 2=clean 16位元組(16*8/2 = 64),代表一個區中64個頁的狀態
Inode(File Segment)
pageno=2的頁為檔案段的元資料資訊描述頁(此處略去pageno=1的插入緩沖位圖資料頁是因為此資料頁與表空間的存盤分配沒有特別大的關系,關聯性不大,后續有機會會專門介紹MySql的插入緩沖相關的內容)
照例先看資料結構圖:

12位元組的檔案鏈表頭以及85個entry,單個entry192位元組,Entry的結構如下

- FSEG ID (8)/FSEG_ID 與entry對應的SegmentId ,為0表示當前segment id暫未使用
- Number of used pages in "NOT_FULL" list (4)/FSEG_NOT_FULL_N_USED FSEG_NOT_FULL鏈表上被使用的Page數量
- List base node for "FREE" list (16)/FSEG_FREE 空閑extent鏈表,完全沒有被使用并分配給該Segment的Extent鏈表
- List base node for "NOT_FULL" list (16)/FSEG_NOT_FULL 至少有一個page分配給當前Segment的Extent鏈表,全部用完時,轉移到FSEG_FULL上,全部釋放時,則歸還給當前表空間FSP_FREE鏈表
- List base node for "FULL" list (16)/FSEG_FULL 分配給當前segment且Page完全使用完的Extent鏈表
- Magic Number = 97937874 (4)/FSEG_MAGIC_N Magic Number,
- Fragment Array Entry 0 (4)/FSEG_FRAG_ARR(0) 屬于該Segment的獨立Page,總是先從全域分配獨立的Page,當填滿32個陣列項時,每次分配時都分配一個完整的Extent,并在xdes_entry中將其Segment ID設定為當前值
- Fragment Array Entry 31 (4)/FSEG_FRAG_ARR(31) 總共能存盤32個資料頁(編號)碎片,可以看到碎片頁的分配數量為什么是32了
小結
可以看到,以下的幾個元資料頁的資訊在結構上有幾個共同點:
- 對于檔案鏈表表頭,均保存了鏈表的長度、表頭、表尾的指標指向
- 對于段、區元資料資訊的描述,有專門的資料描述頁,對于整個表空間檔案存盤空間的管理都是通過這些元資料頁資訊來分配的
- Free/Not Full/Full 鏈表結構分別用于記錄完全空閑(按塊分配)、區域空閑(碎片分配)、滿空間的結構
- 對于存盤空間的管理具備在邏輯上是一個層次化的結構(參見表空間檔案的整體結構圖)
- 頁的大小均是固定的16KB(此處不涉及壓縮資料的范疇),不同的是里面內部的資料結構不同決定了資料頁的型別、用途
- 設計上均采取header/body的形式(計算機科學中很多場景都會使用此中資料組織方式,場景的網路層協議堆疊分層設計)
下面我們可以來看看完整的邏輯、物理結構檔案映射圖:

自底向上,可以分為這幾部分
- 第一層為表空間資料檔案物理結構,資料頁按照16kb的方式劃分,資料頁之間用雙向鏈表的方式串接(后續介紹資料頁結構會描述),每64個資料頁(1M)的空間劃分為一個區
- 第二層為區的邏輯劃分,每一個xdes_entry資料頁描述256個區,xdes_entry資料頁之間的也用雙向鏈表銜接
- 第三層為Inode的描述結構,這里面有資料碎片頁的描述資訊,更多的是對于下層區的管理與描述
- 第四層即為索引段的邏輯結構來,可以看到圖中的箭頭指向以及資料頁的pageno標號
附錄
-
文中圖片檔案出處:
https://github.com/jeremycole/innodb_diagrams -
文章大部分內容思路來源于https://blog.jcole.us/innodb/
英文好的同學可以直接閱讀原版內容
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/286061.html
標籤:MySQL
上一篇:存盤體系
下一篇:mysql-jdbc
