innodb 物理文件解析
1 綜述
innodb的物理文件包括系統(tǒng)表空間文件ibdata徐块,用戶表空間文件ibd麦备,日志文件ib_logfile港准,臨時(shí)表空間文件ibtmp欲鹏,undo獨(dú)立表空間等屿聋。
系統(tǒng)表空間是innodb最重要的文件空扎,它記錄包括元數(shù)據(jù)信息藏鹊,事務(wù)系統(tǒng)信息,ibuf信息转锈,double write等關(guān)鍵信息盘寡。
用戶表空間文件通常分為兩類,一類是當(dāng)innodb_file_per_table打開(kāi)時(shí)黑忱,一個(gè)用戶表空間對(duì)應(yīng)一個(gè)文件宴抚,另外一種則是5.7版本引入的所謂General Tablespace,在滿足一定約束條件下甫煞,可以將多個(gè)表創(chuàng)建到同一個(gè)文件中菇曲。
日志文件主要用于記錄redo log。innodb在所有數(shù)據(jù)變更前抚吠,先寫redo日志常潮。為保證redo日志原子寫入,日志通常以512字節(jié)的block單位寫入楷力。但由于現(xiàn)代文件系統(tǒng)升級(jí)喊式,block_size通常設(shè)置到了4k,因此innodb也提供了一個(gè)選項(xiàng)支持redo日志以4k為單位寫入萧朝。
臨時(shí)表空間文件用于存儲(chǔ)所有非壓縮的臨時(shí)表岔留,第1~32個(gè)臨時(shí)表專用的回滾段也存放在該文件中。由于臨時(shí)表的本身屬性检柬,該文件在重啟時(shí)會(huì)重新創(chuàng)建献联。
undo獨(dú)立表空間是innodb的一個(gè)可選項(xiàng),由innodb_undo_tablespaces配置何址。默認(rèn)情況下里逆,該值為0,即undo數(shù)據(jù)是存儲(chǔ)在ibdata中用爪。innodb_undo_tablespaces 設(shè)置為非0原押,可使得undo 回滾段分配到不同的文件中,目前開(kāi)啟undo tablespace 只能在install階段進(jìn)行偎血。
上述文件除日志文件外诸衔,都具有較為統(tǒng)一的物理結(jié)構(gòu)。所有物理文件由頁(yè)(page 或 block)構(gòu)成颇玷,在未被壓縮情況下署隘,一個(gè)頁(yè)的大小為UNIV_PAGE_SIZE(16384,16K)亚隙。不同用途的頁(yè)具有相同格式的頁(yè)頭(38)和頁(yè)尾(8)磁餐,其中記錄了頁(yè)面校驗(yàn)值,頁(yè)面編號(hào),表空間編號(hào)诊霹,LSN等通用信息羞延,詳見(jiàn)下表。所有page通過(guò)一定方式組織起來(lái)脾还,下面我們分別從物理結(jié)構(gòu)伴箩,邏輯結(jié)構(gòu),文件管理過(guò)程來(lái)具體了解innodb的文件結(jié)構(gòu)鄙漏。
FIL Header / Trailer |
---|
checksum 校驗(yàn)值 (4) |
offset 頁(yè)面編號(hào) (4) |
previous page 前頁(yè)編號(hào)(4) |
next page 后頁(yè)編號(hào) (4) |
lsn for last modification 最后修改的lsn (8) |
page type 頁(yè)面類型 (4) |
flush lsn 刷盤lsn嗤谚,只在page 0保存 (4) |
space id 表空間編號(hào) (4) |
... |
old-style checksum 頁(yè)尾校驗(yàn)值 (4) |
low 32bits of lsn lsn的低4字節(jié) (4) |
2 文件物理結(jié)構(gòu)
2.1 基本物理結(jié)構(gòu)
innodb 的每個(gè)數(shù)據(jù)文件都?xì)w屬于一個(gè)表空間(tablespace),不同的表空間使用一個(gè)唯一標(biāo)識(shí)的space id來(lái)標(biāo)記怔蚌。值得注意的是巩步,系統(tǒng)表空間ibdata雖然包括不同文件ibdata1, ibdata2…,但這些文件邏輯上是相連的桦踊,這些文件同屬于space_id為0的表空間椅野。
表空間內(nèi)部,所有頁(yè)按照簇(extent)為物理單元進(jìn)行劃分和管理籍胯。extent內(nèi)所有頁(yè)面物理相鄰竟闪。對(duì)于不同的page size,對(duì)應(yīng)的extent大小也不同杖狼,對(duì)應(yīng)為:
page size | extent size |
---|---|
4 KiB | 256 pages = 1 MiB |
8 KiB | 128 pages = 1 MiB |
16 KiB | 64 pages = 1 MiB |
32 KiB | 64 pages = 2 MiB |
64 KiB | 64 pages = 4 MiB |
通常情況下炼蛤,extent由64個(gè)物理連續(xù)的頁(yè)組成,表空間可以理解為由一個(gè)個(gè)extent物理相鄰的extent組成蝶涩。為了組織起這些extent理朋,每個(gè)extent都有一個(gè)占40字節(jié)的XDES entry。格式如下:
Macro | bytes | Desc |
---|---|---|
XDES_ID | 8 | 如果該extent歸屬某個(gè)segment的話子寓,則記錄其ID |
XDES_FLST_NODE | 12 | (FLST_NODE_SIZE) 維持extent鏈表的雙向指針節(jié)點(diǎn) |
XDES_STATE | 4 | 該extent的狀態(tài)信息暗挑,包括:XDES_FREE笋除,XDES_FREE_FRAG斜友,XDES_FULL_FRAG,XDES_FSEG |
XDES_BITMAP | 16 | 總共16*8= 128個(gè)bit垃它,用2個(gè)bit表示extent中的一個(gè)page鲜屏,一個(gè)bit表示該page是否是空閑的(XDES_FREE_BIT),另一個(gè)保留位国拇,尚未使用(XDES_CLEAN_BIT) |
利用XDES entry洛史,我們可以方便地了解到該extent每頁(yè)空閑與否,以及其當(dāng)前狀態(tài)酱吝。
所有XDES entry都統(tǒng)一放在extent描述頁(yè)中也殖,一個(gè)extent描述頁(yè)至多存放256個(gè)XDES entry,用于管理其隨后物理相鄰的256個(gè)extent(256*64 = 16384 page),如下圖所示所示:
由圖可見(jiàn)忆嗜,每個(gè)XDES entry有嚴(yán)格對(duì)應(yīng)的頁(yè)面己儒,其對(duì)應(yīng)頁(yè)面上下界可以描述為:
min_scope = extent 描述頁(yè) page_no + xdes 編號(hào) * 64
max_scope =( extent 描述頁(yè) page_no + xdes 編號(hào) * 64 )+63
值得注意的是,其中 page 0的extent描述頁(yè)還記錄了與該table space相關(guān)的信息(FSP HEADER)捆毫,其類型為FIL_PAGE_TYPE_FSP_HDR闪湾。其他extent描述頁(yè)的類型相同,為FIL_PAGE_TYPE_XDES绩卤。
2.2 系統(tǒng)數(shù)據(jù)頁(yè)
系統(tǒng)表空間(ibdata)不僅存放了SYS_TABLE / SYS_INDEX 等系統(tǒng)表的數(shù)據(jù)途样,還存放了回滾信息(undo),插入緩沖索引頁(yè)(IBUF bitmap)濒憋,系統(tǒng)事務(wù)信息(trx_sys)何暇,二次寫緩沖(double write)等信息。
innodb中核心的數(shù)據(jù)都存放在ibdata中的系統(tǒng)數(shù)據(jù)頁(yè)中跋炕。系統(tǒng)數(shù)據(jù)頁(yè)主要包括:FIL_PAGE_TYPE_FSP_HDR, FIL_PAGE_IBUF_BITMAP, FIL_PAGE_TYPE_SYS, IBUF_ROOT_PAGE, FIL_PAGE_TYPE_TRX_SYS, FIL_PAGE_TYPE_SYS, DICT_HDR_PAGE等赖晶。
FIL_PAGE_TYPE_FSP_HDR/FIL_PAGE_TYPE_XDES
extent描述頁(yè)(page 0/16384/32768/... ),上文已述及辐烂,故不再展開(kāi)遏插。FIL_PAGE_IBUF_BITMAP
ibdata第2個(gè)page類型為FIL_PAGE_IBUF_BITMAP,主要用于跟蹤隨后的每個(gè)page的change buffer信息纠修。由于bitmap page的空間有限胳嘲,同樣每隔256個(gè)extent Page之后,也會(huì)在XDES PAGE之后創(chuàng)建一個(gè)ibuf bitmap page扣草。FIL_PAGE_INODE
ibdata的第3個(gè)page的類型為FIL_PAGE_INODE了牛,用于管理數(shù)據(jù)文件中的segment,每個(gè)inode頁(yè)可以存儲(chǔ)FSP_SEG_INODES_PER_PAGE(默認(rèn)為85)個(gè)記錄辰妙。segment是表空間管理的邏輯單位鹰祸,每個(gè)索引占用2個(gè)segment,分別用于管理葉子節(jié)點(diǎn)和非葉子節(jié)點(diǎn)密浑。關(guān)于segment的詳細(xì)介紹蛙婴,將在第三節(jié)展開(kāi)。FSP_IBUF_HEADER_PAGE_NO 和 FSP_IBUF_TREE_ROOT_PAGE_NO
上述兩個(gè)頁(yè)分別是Ibdata的第4個(gè)page和第5個(gè)page尔破。change buffer本質(zhì)上也是btree結(jié)構(gòu)街图,其root頁(yè)固定在第5個(gè)page FSP_IBUF_TREE_ROOT_PAGE_NO。 由于FSP_IBUF_TREE_ROOT_PAGE_NO中原先用于記錄leaf inode entry的字段被用于維護(hù)空閑page鏈表了懒构,因此ibdata需要使用第4頁(yè)FSP_IBUF_TREE_ROOT_PAGE_NO 來(lái)對(duì)ibuf進(jìn)行空間管理餐济。FSP_TRX_SYS_PAGE_NO
ibdata第6個(gè)page的類型為FSP_TRX_SYS_PAGE_NO,記錄了innodb重要的事務(wù)系統(tǒng)信息胆剧,包括持久化的最大事務(wù)ID絮姆,以及128個(gè)rseg(rollback segment)的地址,double write位置等。這128個(gè)rseg中篙悯,rseg0固定在ibdata中冤灾,rseg1-rseg32用于管理臨時(shí)表,rseg33-rseg128 當(dāng)未開(kāi)啟undo獨(dú)立表空間 (innodb undo tablespace = 0)時(shí)辕近,仍放在ibdata中韵吨,否則放在undo獨(dú)立表空間中。每個(gè)rseg中記錄了1024個(gè)slot移宅,每個(gè)slot也都可對(duì)應(yīng)一個(gè)事務(wù)归粉,用于管理該事務(wù)的undo記錄。由于每個(gè)slot也需要申請(qǐng)和釋放page漏峰,因此每個(gè)slot也對(duì)應(yīng)一個(gè)segment(空間管理邏輯單位)糠悼。FSP_DICT_HDR_PAGE_NO
ibdata第8個(gè)page的類型為FSP_DICT_HDR_PAGE_NO,用來(lái)存儲(chǔ)數(shù)據(jù)詞典表的信息 浅乔。該頁(yè)存儲(chǔ)了SYS_TABLES倔喂,SYS_TABLE_IDS,SYS_COLUMNS靖苇,SYS_INDEXES和SYS_FIELDS的root page席噩,以及當(dāng)前最大的TABLE_ID/ROW_ID/INDEX_ID/SPACE_ID。當(dāng)對(duì)用戶表操作時(shí)贤壁,需要先從數(shù)據(jù)字典表中獲取到用戶表對(duì)應(yīng)的表空間悼枢,以及其索引root頁(yè)的page_no,才能定位到具體數(shù)據(jù)的位置脾拆,對(duì)其進(jìn)行增刪改查馒索。(只有拿到數(shù)據(jù)詞典表,才能根據(jù)其中存儲(chǔ)的表信息名船,進(jìn)一步找到其對(duì)應(yīng)的表空間绰上,以及表的聚集索引所在的page no)double write buffer
innodb使用double write buffer來(lái)防止數(shù)據(jù)頁(yè)的部分寫問(wèn)題,在寫一個(gè)數(shù)據(jù)頁(yè)之前渠驼,總是先寫double write buffer蜈块,再寫數(shù)據(jù)文件。當(dāng)崩潰恢復(fù)時(shí)渴邦,如果數(shù)據(jù)文件中page損壞疯趟,會(huì)嘗試從dblwr中恢復(fù)拘哨。double write buffer總共128個(gè)page谋梭,劃分為兩個(gè)block。由于dblwr在安裝實(shí)例時(shí)已經(jīng)初始化好了倦青,這兩個(gè)block在Ibdata中具有固定的位置瓮床,page64 ~127 劃屬第一個(gè)block,page 128 ~191劃屬第二個(gè)block。
當(dāng)innodb_file_per_table為off狀態(tài)時(shí)隘庄,所有用戶表也將和SYS_TABLE / SYS_INDEX 等系統(tǒng)表一樣踢步,存儲(chǔ)在ibdata中。當(dāng)開(kāi)啟innodb_file_per_table時(shí)丑掺,innodb會(huì)為每一個(gè)用戶表建立一個(gè)獨(dú)立的ibd文件获印。該ibd文件存放了對(duì)應(yīng)用戶表的索引數(shù)據(jù)和插入緩沖bitmap。 而該表的回滾數(shù)據(jù)(undo)仍記錄在ibdata中街州。
3 文件邏輯結(jié)構(gòu)
3.1 基本邏輯結(jié)構(gòu)
innodb為了組織各extent,在表空間的第一個(gè)page還維護(hù)了三個(gè)extent的鏈表:FSP_FREE、FSP_FREE_FRAG饲常、FSP_FULL_FRAG涯呻。分別將extent完全未被使用,部分被使用面徽,完全被使用的Xdes entry串聯(lián)起來(lái)艳丛。
innodb的邏輯管理管理單位是段(segment 或稱 inode)。為節(jié)省空間趟紊,每個(gè)segment都先從表空間FREE_FRAG中分配32個(gè)頁(yè)(FSEG_FRAG_ARR)氮双,當(dāng)這些32個(gè)頁(yè)面不夠使用時(shí)。按照以下原則進(jìn)行擴(kuò)展:如果當(dāng)前小于1個(gè)extent霎匈,則擴(kuò)展到1個(gè)extent滿眶蕉;當(dāng)表空間小于32MB時(shí),每次擴(kuò)展一個(gè)extent唧躲;大于32MB時(shí)造挽,每次擴(kuò)展4個(gè)extent。
在為segment分配空閑的extent時(shí)弄痹,如果表空間FSP_FREE上沒(méi)有空閑的segment饭入,則會(huì)為FSP_FREE重新初始化一些空閑extent。extent的分配類似于實(shí)現(xiàn)了一套借還機(jī)制肛真。segment向表空間租借extent谐丢,只有segment退還該空間時(shí),該extent才能重新出現(xiàn)在FSP_FREE/FSP_FULL_FRAG/FSP_FULL中蚓让。
segment內(nèi)部為了管理起這些分配來(lái)的extent乾忱。也有三個(gè)extent鏈表:FSEG_FREE、FSEG_NOT_FULL历极、FSEG_FULL窄瘟。也分別對(duì)應(yīng)extent完全未被使用,部分被使用趟卸,完全被使用的Xdes entry蹄葱。這三個(gè)鏈表的地址被記錄在inode entry中氏义。INode entry的具體結(jié)構(gòu)如下表所示:
Macro | bytes | Desc |
---|---|---|
FSEG_ID | 8 | 該inode歸屬的Segment ID,若值為0表示該slot未被使用 |
FSEG_NOT_FULL_N_USED | 4 | FSEG_NOT_FULL鏈表上被使用的Page數(shù)量 |
FSEG_FREE | 16 | 完全沒(méi)有被使用并分配給該Segment的extent鏈表 |
FSEG_NOT_FULL | 16 | 至少有一個(gè)page分配給當(dāng)前Segment的extent鏈表图云,全部用完時(shí)惯悠,轉(zhuǎn)移到FSEG_FULL上,全部釋放時(shí)竣况,則歸還給當(dāng)前表空間FSP_FREE鏈表 |
FSEG_FULL | 16 | 分配給當(dāng)前segment且Page完全使用完的extent鏈表 |
FSEG_MAGIC_N | 4 | Magic Number |
FSEG_FRAG_ARR 0 | 4 | 屬于該Segment的獨(dú)立Page克婶。總是先從全局分配獨(dú)立的Page丹泉,當(dāng)填滿32個(gè)數(shù)組項(xiàng)時(shí)鸠补,就在每次分配時(shí)都分配一個(gè)完整的extent,并在XDES PAGE中將其Segment ID設(shè)置為當(dāng)前值 |
…… | …… | …… |
FSEG_FRAG_ARR 31 | 4 | 總共存儲(chǔ)32個(gè)記錄項(xiàng) |
從上文我們可以看到嘀掸,innodb通過(guò)inode entry來(lái)管理每個(gè)Segment占用的數(shù)據(jù)頁(yè)紫岩,每個(gè)segment可以看做一個(gè)文件頁(yè)維護(hù)單元。inode entry所在的inode page有可能存放滿睬塌,因此又通過(guò)頭Page(FIL_PAGE_TYPE_FSP_HDR)中維護(hù)了兩個(gè)inode Page鏈表FSP_SEG_INODES_FULL和FSP_SEG_INODES_FREE泉蝌。前者對(duì)應(yīng)沒(méi)有空閑inode entry的inode page鏈表,后者對(duì)應(yīng)的至少有一個(gè)空閑inode entry的inode page鏈表揩晴。
3.2 索引
ibd文件中真正構(gòu)建起用戶數(shù)據(jù)的結(jié)構(gòu)是BTREE勋陪。表中的每一個(gè)索引對(duì)應(yīng)一個(gè)btree。主鍵(cluster index)對(duì)應(yīng)btree的葉子節(jié)點(diǎn)上記錄了行的全部列數(shù)據(jù)(加上transaction id列及rollback ptr)硫兰。當(dāng)表中無(wú)主鍵時(shí)诅愚,innodb會(huì)為該表每一行分配一個(gè)唯一的rowID,并基于它構(gòu)造btree劫映。如果表中存在二級(jí)索引(secondary index)违孝,那么其BTREE葉子節(jié)點(diǎn)存儲(chǔ)了鍵值加上cluster index索引鍵值。
每個(gè)btree使用兩個(gè)Segment來(lái)管理數(shù)據(jù)頁(yè)泳赋,一個(gè)管理葉子節(jié)點(diǎn)(leaf segment)雌桑,一個(gè)管理非葉子節(jié)點(diǎn)(non-leaf segment)。這兩個(gè)segment的inode entry地址記錄在btree的root page中祖今。root page分配在non-leaf segment第一個(gè)碎片頁(yè)上(FSEG_FRAG_ARR)校坑。
當(dāng)對(duì)一個(gè)表進(jìn)行增刪改查的操作時(shí),我們首先需要從ibdata的第8頁(yè)FSP_DICT_HDR_PAGE_NO中l(wèi)oad改表的元數(shù)據(jù)信息千诬,從SYS_INDEXES表中獲取該表各索引對(duì)應(yīng)的root page no耍目,進(jìn)而通過(guò)root page對(duì)這個(gè)表的用戶數(shù)據(jù)btree進(jìn)行操作。表空間的邏輯結(jié)構(gòu)如下圖所示:
3.3 索引頁(yè)數(shù)據(jù)
索引最基本的頁(yè)類型為FIL_PAGE_INDEX徐绑,其結(jié)構(gòu)如下表所示邪驮。Index Header中記錄了page所在Btree層次,所屬index ID泵三,page directory槽數(shù)等與頁(yè)面相關(guān)的信息耕捞。Fseg Header中記錄了該index的leaf-segment和non-leaf segment的inode entry,system records包括infimum和supremum烫幕,分別代表該頁(yè)最小俺抽、最大記錄虛擬記錄。page directory是頁(yè)內(nèi)記錄的索引较曼。Btree只能檢索到記錄所在的page磷斧,page內(nèi)的檢索需要使用到通過(guò)page directory構(gòu)建起的二分查找。
Index Page |
---|
FILHeader (38) |
Index Header (36) |
Fseg Header (20) |
System Records (26) |
User Records |
Free Space |
Page Directory |
FIL trailer (8) |
innodb按行存放數(shù)據(jù)捷犹。當(dāng)前MySQL支持等行格式包括antelope(compact和redundant)弛饭,和barracuda(dynamic和compressed)。barracuda與antelope主要區(qū)別在于其處理行外數(shù)據(jù)等方式萍歉,barracuda只存儲(chǔ)行外數(shù)據(jù)等地址指針侣颂,不像antelope一樣存放768字節(jié)的行前綴內(nèi)容。以compact行格式為例介紹行格式的具體內(nèi)容枪孩,如下圖所示憔晒,行由變長(zhǎng)字段長(zhǎng)度列表、NULL標(biāo)志位蔑舞、記錄頭信息拒担、系統(tǒng)列、用戶列組成攻询。記錄頭信息中存放刪除標(biāo)志从撼、列總數(shù)、下行相對(duì)偏移等信息钧栖、系統(tǒng)列包括rowID低零、transactionID、rollback pointer等組成拯杠。
變長(zhǎng)字段長(zhǎng)度 | NULL 標(biāo)志位 | 記錄頭信息 | Field 1 | ... | Field N |
---|
4 文件管理過(guò)程
下面用精簡(jiǎn)后的源碼來(lái)簡(jiǎn)單介紹innodb文件的管理過(guò)程:
4.1 btree的創(chuàng)建過(guò)程
btree的創(chuàng)建過(guò)程可以概括為:先創(chuàng)建non_leaf segment毁兆,利用non_leaf segment的首頁(yè)(即32個(gè)碎片頁(yè)中第一頁(yè))作為root page;然后創(chuàng)建leaf_segment阴挣;最后對(duì)root page進(jìn)行必要的初始化气堕。詳細(xì)過(guò)程請(qǐng)參考以下代碼:
btr_create(
ulint type,
ulint space,
const page_size_t& page_size,
index_id_t index_id,
dict_index_t* index,
const btr_create_t* btr_redo_create_info,
mtr_t* mtr)
{
/* index tree 的segment headers 存儲(chǔ)于新分配的root page中,ibuf tree的
segment headers放在獨(dú)立的ibuf header page中畔咧。以下代碼屏蔽了ibuf tree的
創(chuàng)建邏輯茎芭,重點(diǎn)介紹index tree的創(chuàng)建過(guò)程 */
/* 局部變量 */
...
/* 創(chuàng)建一個(gè)non_leaf segment段,并將段的地址存儲(chǔ)到段首頁(yè)偏移為
PAGE_HEADER + PAGE_BTR_SEG_TOP的位置誓沸,用block記錄下non_leaf segment
段首頁(yè)page對(duì)應(yīng)的block梅桩,該block將作為該btree的root page */
block = fseg_create(space, 0,
PAGE_HEADER + PAGE_BTR_SEG_TOP, mtr);
if (block == NULL) {
return(FIL_NULL);
}
/* 記錄下root page的信息 */
page_no = block->page.id.page_no();
frame = buf_block_get_frame(block);
/* 創(chuàng)建leaf_segment,并將段首存儲(chǔ)到root page上偏移為
PAGE_HEADER + PAGE_BTR_SEG_LEAF的位置 */
if (!fseg_create(space, page_no,
PAGE_HEADER + PAGE_BTR_SEG_LEAF, mtr)) {
/* 沒(méi)有足夠的空間分配新的segment拜隧,需要釋放掉已分配的root page */
btr_free_root(block, mtr);
return(FIL_NULL);
}
/* 在root page上做index page的初始化宿百,根據(jù)頁(yè)面壓縮與否做不同處理 */
page_zip = buf_block_get_page_zip(block);
if (page_zip) {
/* 其他邏輯 */
page = page_create_zip(block, index, 0, 0, NULL, mtr);
} else {
/* 其他邏輯 */
page = page_create(block, mtr,
dict_table_is_comp(index->table),
dict_index_is_spatial(index));
}
/* 在root page上設(shè)置其所在的index id */
btr_page_set_index_id(page, page_zip, index_id, mtr);
/* 將root page的前后頁(yè)面設(shè)置為NULL */
btr_page_set_next(page, page_zip, FIL_NULL, mtr);
btr_page_set_prev(page, page_zip, FIL_NULL, mtr);
/* 其他邏輯 */
/* 返回root page的頁(yè)面號(hào) */
return(page_no);
}
4.2 segment的創(chuàng)建過(guò)程
segment的創(chuàng)建過(guò)程比較簡(jiǎn)單:先在inode page中為segment分配一個(gè)inode entry趁仙,然后再inode entry上進(jìn)行初始化,更新space header里的最大segment id垦页,即可雀费。需要注意的是:當(dāng)傳入的page 為0 時(shí),意味著要?jiǎng)?chuàng)建一個(gè)獨(dú)立的segment痊焊,需要將當(dāng)前的inode entry地址記錄在段首page中盏袄,并返回;當(dāng)傳入的page非0時(shí)薄啥,segment需要在指定的page的指定位置記錄下當(dāng)前的inode entry地址辕羽。詳細(xì)過(guò)程請(qǐng)參考代碼:
buf_block_t*
fseg_create_general(
/*================*/
ulint space_id,/*!< in: space id */
ulint page, /*!< in: page where the segment header is placed: if
this is != 0, the page must belong to another segment,
if this is 0, a new page will be allocated and it
will belong to the created segment */
ulint byte_offset, /*!< in: byte offset of the created segment header
on the page */
ibool has_done_reservation, /*!< in: TRUE if the caller has already
done the reservation for the pages with
fsp_reserve_free_extents (at least 2 extents: one for
the inode and the other for the segment) then there is
no need to do the check for this individual
operation */
mtr_t* mtr) /*!< in/out: mini-transaction */
{
/* 局部變量 */
...
/* 如果傳入的page是0,則創(chuàng)建一個(gè)獨(dú)立的段垄惧,并把segment header的信息
存儲(chǔ)在段首page中刁愿。如果傳入page是非0,則這是一個(gè)非獨(dú)立段到逊,需要將
segment header的信息存儲(chǔ)在指定page的指定位置上 */
if (page != 0) {
/* 獲取指定page */
block = buf_page_get(page_id_t(space_id, page), page_size,
RW_SX_LATCH, mtr);
header = byte_offset + buf_block_get_frame(block);
}
/* 其他邏輯 */
/* 獲取space header和inode_entry */
space_header = fsp_get_space_header(space_id, page_size, mtr);
inode = fsp_alloc_seg_inode(space_header, mtr);
if (inode == NULL) {
goto funct_exit;
}
/* 獲取當(dāng)前表空間最大segment id酌毡,并更新表空間最大
segment id */
seg_id = mach_read_from_8(space_header + FSP_SEG_ID);
mlog_write_ull(space_header + FSP_SEG_ID, seg_id + 1, mtr);
/* 初始化inode entry的segment id 和 FSEG_NOT_FULL_N_USED */
mlog_write_ull(inode + FSEG_ID, seg_id, mtr);
mlog_write_ulint(inode + FSEG_NOT_FULL_N_USED, 0, MLOG_4BYTES, mtr);
/* 初始化inode entry的三個(gè)extent鏈表 */
flst_init(inode + FSEG_FREE, mtr);
flst_init(inode + FSEG_NOT_FULL, mtr);
flst_init(inode + FSEG_FULL, mtr);
/* 初始化innode entry的32個(gè)碎片頁(yè) */
mlog_write_ulint(inode + FSEG_MAGIC_N, FSEG_MAGIC_N_VALUE,
MLOG_4BYTES, mtr);
for (i = 0; i < FSEG_FRAG_ARR_N_SLOTS; i++) {
fseg_set_nth_frag_page_no(inode, i, FIL_NULL, mtr);
}
/* 如果傳入的page是0,則分配一個(gè)段首page */
if (page == 0) {
block = fseg_alloc_free_page_low(space, page_size,
inode, 0, FSP_UP, RW_SX_LATCH,
mtr, mtr
#ifdef UNIV_DEBUG
, has_done_reservation
#endif /* UNIV_DEBUG */
);
header = byte_offset + buf_block_get_frame(block);
mlog_write_ulint(buf_block_get_frame(block) + FIL_PAGE_TYPE,
FIL_PAGE_TYPE_SYS, MLOG_2BYTES, mtr);
}
/* 在page指定位置記錄segment header蕾管,segment header由
inode page所在的space id枷踏,page no, 以及inode entry的在
inode page 中的頁(yè)內(nèi)偏移組成 */
mlog_write_ulint(header + FSEG_HDR_OFFSET,
page_offset(inode), MLOG_2BYTES, mtr);
mlog_write_ulint(header + FSEG_HDR_PAGE_NO,
page_get_page_no(page_align(inode)),
MLOG_4BYTES, mtr);
mlog_write_ulint(header + FSEG_HDR_SPACE, space_id, MLOG_4BYTES, mtr);
funct_exit:
DBUG_RETURN(block);
}
4.3 extent的分配過(guò)程
表空間分配extent的邏輯比較簡(jiǎn)單,直接查詢FSP_FREE上有沒(méi)有剩余的extent即可掰曾,沒(méi)有的話就為FSP_FREE重新初始化一些extent旭蠕。詳細(xì)邏輯如下:
static
xdes_t*
fsp_alloc_free_extent(
ulint space_id,
const page_size_t& page_size,
ulint hint,
mtr_t* mtr)
{
/* 局部變量 */
...
/* 獲取space header */
header = fsp_get_space_header(space_id, page_size, mtr);
/* 獲取hint頁(yè)所在的xdes entry */
descr = xdes_get_descriptor_with_space_hdr(
header, space_id, hint, mtr, false, &desc_block);
fil_space_t* space = fil_space_get(space_id);
/* 當(dāng)hint頁(yè)所在的xdes entry的狀態(tài)是XDES_FREE時(shí),直接將其摘下返回旷坦,
否則嘗試從FSP_FREE中為segment分配extent掏熬。如果FSP_FREE為空,
則需要進(jìn)一步從未初始化的空間中為FSP_FREE新分配一些extent秒梅,
并從新的FSP_FREE中取出第一個(gè)extent返回 */
if (descr && (xdes_get_state(descr, mtr) == XDES_FREE)) {
/* Ok, we can take this extent */
} else {
/* Take the first extent in the free list */
first = flst_get_first(header + FSP_FREE, mtr);
if (fil_addr_is_null(first)) {
fsp_fill_free_list(false, space, header, mtr);
first = flst_get_first(header + FSP_FREE, mtr);
}
/* 分配失敗 */
if (fil_addr_is_null(first)) {
return(NULL); /* No free extents left */
}
descr = xdes_lst_get_descriptor(
space_id, page_size, first, mtr);
}
/* 將分配到的extent從FSP_FREE中刪除 */
flst_remove(header + FSP_FREE, descr + XDES_FLST_NODE, mtr);
space->free_len--;
return(descr);
}
當(dāng)為segment分配extent時(shí)稍微復(fù)雜一些:先檢查FSEG_ FREE中是否有剩余的extent旗芬,如果沒(méi)有再用fsp_alloc_free_extent從表空間中申請(qǐng)extent。在第二種情況下捆蜀,F(xiàn)SEG_ FREE中的extent不足疮丛,因此還會(huì)進(jìn)一步嘗試為FSEG_FREE分配更多extent。詳細(xì)過(guò)程如下:
static
xdes_t*
fseg_alloc_free_extent(
fseg_inode_t* inode,
ulint space,
const page_size_t& page_size,
mtr_t* mtr)
{
/* 局部變量 */
...
/* 如果FSEG_FREE非空辆它,則從其中為segment分配extent誊薄,如果FSEG_FREE為空,
則從調(diào)用fsp_alloc_free_extent 為當(dāng)前segment分配extent */
if (flst_get_len(inode + FSEG_FREE) > 0) {
first = flst_get_first(inode + FSEG_FREE, mtr);
descr = xdes_lst_get_descriptor(space, page_size, first, mtr);
} else {
descr = fsp_alloc_free_extent(space, page_size, 0, mtr);
if (descr == NULL) {
return(NULL);
}
/* 將從space申請(qǐng)到的extent設(shè)置為segment私有狀態(tài)(XDES_FSEG)锰茉,
將改extent加入到FSEG_FREE中 */
seg_id = mach_read_from_8(inode + FSEG_ID);
xdes_set_state(descr, XDES_FSEG, mtr);
mlog_write_ull(descr + XDES_ID, seg_id, mtr);
flst_add_last(inode + FSEG_FREE, descr + XDES_FLST_NODE, mtr);
/* 當(dāng)前FSEP_FREE中剩余的extent不多呢蔫,嘗試為當(dāng)前segment分配更多
物理相鄰的extent */
fseg_fill_free_list(inode, space, page_size,
xdes_get_offset(descr) + FSP_EXTENT_SIZE,
mtr);
}
return(descr);
}
4.4 page的分配過(guò)程
表空間page的分配過(guò)程如下:先查看hint_page所在的extent是否適合分配空閑頁(yè)面,不適合的話飒筑,則嘗試從FSP_FREE_FRAG鏈表中尋找空閑頁(yè)面片吊。如果FSP_FREE_FRAG為空绽昏,則新分配一個(gè)extent,將其添加到FSP_FREE_FRAG中俏脊,并在其中分配空閑頁(yè)面全谤。
static MY_ATTRIBUTE((warn_unused_result))
buf_block_t*
fsp_alloc_free_page(
ulint space,
const page_size_t& page_size,
ulint hint,
rw_lock_type_t rw_latch,
mtr_t* mtr,
mtr_t* init_mtr)
{
/* 局部變量 */
...
/* 獲取表空間header 和 hint page所在extent的xdes entry */
header = fsp_get_space_header(space, page_size, mtr);
descr = xdes_get_descriptor_with_space_hdr(header, space, hint, mtr);
/* 如果xdes entry的狀態(tài)是XDES_FREE_FRAG,那就直接從該extent中分配page联予,
否則從FSP_FREE_FRAG中去尋找空閑page */
if (descr && (xdes_get_state(descr, mtr) == XDES_FREE_FRAG)) {
/* Ok, we can take this extent */
} else {
/* Else take the first extent in free_frag list */
first = flst_get_first(header + FSP_FREE_FRAG, mtr);
/* 嘗試從FSP_FREE_FRAG中尋找空閑頁(yè)面啼县,當(dāng)FSP_FREE_FRAG鏈表為空時(shí)材原,
需要使用fsp_alloc_free_extent分配一個(gè)新的extent沸久,將該extent加入
FSP_FREE_FRAG,并在其中分配空閑page */
if (fil_addr_is_null(first)) {
descr = fsp_alloc_free_extent(space, page_size,
hint, mtr);
if (descr == NULL) {
/* No free space left */
return(NULL);
}
xdes_set_state(descr, XDES_FREE_FRAG, mtr);
flst_add_last(header + FSP_FREE_FRAG,
descr + XDES_FLST_NODE, mtr);
} else {
descr = xdes_lst_get_descriptor(space, page_size,
first, mtr);
}
/* Reset the hint */
hint = 0;
}
/* 從找到的extent中分配一個(gè)空閑頁(yè)面 */
free = xdes_find_bit(descr, XDES_FREE_BIT, TRUE,
hint % FSP_EXTENT_SIZE, mtr);
if (free == ULINT_UNDEFINED) {
ut_print_buf(stderr, ((byte*) descr) - 500, 1000);
putc('\n', stderr);
ut_error;
}
page_no = xdes_get_offset(descr) + free;
/* 其他邏輯 */
/* 在fsp_alloc_from_free_frag中設(shè)置分配page的XDES_FREE_BIT為false,
表示被占用余蟹;遞增頭page的FSP_FRAG_N_USED字段卷胯;如果該extent被用滿了,
就將其從FSP_FREE_FRAG移除威酒,并加入到FSP_FULL_FRAG鏈表中窑睁,更新FSP_FRAG_N_USED的值 */
fsp_alloc_from_free_frag(header, descr, free, mtr);
/* 對(duì)Page內(nèi)容進(jìn)行初始化后返回 */
return(fsp_page_create(page_id_t(space, page_no), page_size,
rw_latch, mtr, init_mtr));
}
為了能夠使得segment內(nèi)邏輯上相鄰的節(jié)點(diǎn)在物理上也盡量相鄰,盡量提高表空間的利用率葵孤,在segment中分配page的邏輯較為復(fù)雜担钮。詳細(xì)過(guò)程如下所述:
static
buf_block_t*
fseg_alloc_free_page_low(
fil_space_t* space,
const page_size_t& page_size,
fseg_inode_t* seg_inode,
ulint hint,
byte direction,
rw_lock_type_t rw_latch,
mtr_t* mtr,
mtr_t* init_mtr
#ifdef UNIV_DEBUG
, ibool has_done_reservation
#endif /* UNIV_DEBUG */
)
{
/* 局部變量 */
...
/* 計(jì)算當(dāng)前segment使用的和占用的page數(shù)。前者統(tǒng)計(jì)的統(tǒng)計(jì)方法為
累加32個(gè)碎片頁(yè)中已使用的數(shù)量尤仍,F(xiàn)SEG_FULL/FSEG_NOT_FULL中已使
用page的數(shù)量箫津,后者的統(tǒng)計(jì)方法為累加32個(gè)碎片頁(yè)已使用數(shù)量,
FSEG_FULL/FSEG_NOT_FULL/FSEG_FREE三個(gè)鏈表中總page數(shù)*/
reserved = fseg_n_reserved_pages_low(seg_inode, &used, mtr);
/* 獲取表空間header 和 hint page所在extent的xdes entry */
space_header = fsp_get_space_header(space_id, page_size, mtr);
descr = xdes_get_descriptor_with_space_hdr(space_header, space_id,
hint, mtr);
if (descr == NULL) {
/* 說(shuō)明hint page在free limit之外宰啦,將hint page置0苏遥,取消hint page的作用*/
hint = 0;
descr = xdes_get_descriptor(space_id, hint, page_size, mtr);
}
/* In the big if-else below we look for ret_page and ret_descr */
/*-------------------------------------------------------------*/
if ((xdes_get_state(descr, mtr) == XDES_FSEG)
&& mach_read_from_8(descr + XDES_ID) == seg_id
&& (xdes_mtr_get_bit(descr, XDES_FREE_BIT,
hint % FSP_EXTENT_SIZE, mtr) == TRUE)) {
take_hinted_page:
/* 1. hint page所在的extent屬于當(dāng)前segment,并且
hint page也是空閑狀態(tài)赡模,這是最理想的情況 */
ret_descr = descr;
ret_page = hint;
goto got_hinted_page;
/*-----------------------------------------------------------*/
} else if (xdes_get_state(descr, mtr) == XDES_FREE
&& reserved - used < reserved / FSEG_FILLFACTOR
&& used >= FSEG_FRAG_LIMIT) {
/* 2. segment空間利用率高于臨界值(7/8 田炭,F(xiàn)SEG_FILLFACTOR),
并且hint page所在的extent處于XDES_FREE狀態(tài)漓柑,直接將該extent從
FSP_FREE摘下教硫,分配至segment的FSEG_FREE中,返回hint page */
ret_descr = fsp_alloc_free_extent(
space_id, page_size, hint, mtr);
xdes_set_state(ret_descr, XDES_FSEG, mtr);
mlog_write_ull(ret_descr + XDES_ID, seg_id, mtr);
flst_add_last(seg_inode + FSEG_FREE,
ret_descr + XDES_FLST_NODE, mtr);
/* 在利用率條件允許的情況下辆布,為segment的FSEG_FREE多分配幾個(gè)
物理相鄰的extent */
fseg_fill_free_list(seg_inode, space_id, page_size,
hint + FSP_EXTENT_SIZE, mtr);
goto take_hinted_page;
/*-----------------------------------------------------------*/
} else if ((direction != FSP_NO_DIR)
&& ((reserved - used) < reserved / FSEG_FILLFACTOR)
&& (used >= FSEG_FRAG_LIMIT)
&& (!!(ret_descr
= fseg_alloc_free_extent(
seg_inode, space_id, page_size, mtr)))) {
/* 3. 當(dāng)利用率小于臨界值栋豫,不建議分配新的extent,避免空間浪費(fèi)谚殊,
此時(shí)從FSEG_FREE中獲取空閑extent丧鸯,用于分配新的page */
ret_page = xdes_get_offset(ret_descr);
if (direction == FSP_DOWN) {
ret_page += FSP_EXTENT_SIZE - 1;
}
} else if ((xdes_get_state(descr, mtr) == XDES_FSEG)
&& mach_read_from_8(descr + XDES_ID) == seg_id
&& (!xdes_is_full(descr, mtr))) {
/* 4. 當(dāng)hint page所在的extent屬于當(dāng)前segment時(shí),該extent內(nèi)如有空閑page嫩絮,
將其返回 */
ret_descr = descr;
ret_page = xdes_get_offset(ret_descr)
+ xdes_find_bit(ret_descr, XDES_FREE_BIT, TRUE,
hint % FSP_EXTENT_SIZE, mtr);
} else if (reserved - used > 0) {
/* 5. 如果該segment占用的page數(shù)大于實(shí)用的page數(shù)丛肢,說(shuō)明該segment還有空
閑的page围肥,則依次先看FSEG_NOT_FULL鏈表上是否有未滿的extent,如果沒(méi)有蜂怎,
再看FSEG_FREE鏈表上是否有完全空閑的extent */
fil_addr_t first;
if (flst_get_len(seg_inode + FSEG_NOT_FULL) > 0) {
first = flst_get_first(seg_inode + FSEG_NOT_FULL,
mtr);
} else if (flst_get_len(seg_inode + FSEG_FREE) > 0) {
first = flst_get_first(seg_inode + FSEG_FREE, mtr);
} else {
return(NULL);
}
ret_descr = xdes_lst_get_descriptor(space_id, page_size,
first, mtr);
ret_page = xdes_get_offset(ret_descr)
+ xdes_find_bit(ret_descr, XDES_FREE_BIT, TRUE,
0, mtr);
} else if (used < FSEG_FRAG_LIMIT) {
/* 6. 當(dāng)前segment的32個(gè)碎片頁(yè)尚未使用完畢穆刻,使用fsp_alloc_free_page從
表空間FSP_FREE_FRAG中分配獨(dú)立的page,并加入到該inode的frag array page
數(shù)組中 */
buf_block_t* block = fsp_alloc_free_page(
space_id, page_size, hint, rw_latch, mtr, init_mtr);
if (block != NULL) {
/* Put the page in the fragment page array of the
segment */
n = fseg_find_free_frag_page_slot(seg_inode, mtr);
fseg_set_nth_frag_page_no(
seg_inode, n, block->page.id.page_no(),
mtr);
}
return(block);
} else {
/* 7. 當(dāng)上述情況都不滿足時(shí)杠步,直接使用fseg_alloc_free_extent分配一個(gè)空閑
extent氢伟,并從其中取一個(gè)page返回 */
ret_descr = fseg_alloc_free_extent(seg_inode,
space_id, page_size, mtr);
if (ret_descr == NULL) {
ret_page = FIL_NULL;
ut_ad(!has_done_reservation);
} else {
ret_page = xdes_get_offset(ret_descr);
}
}
/* page分配失敗 */
if (ret_page == FIL_NULL) {
return(NULL);
}
got_hinted_page:
/* 將可用的hint page標(biāo)記為used狀態(tài) */
if (ret_descr != NULL) {
fseg_mark_page_used(seg_inode, ret_page, ret_descr, mtr);
}
/* 對(duì)Page內(nèi)容進(jìn)行初始化后返回 */
return(fsp_page_create(page_id_t(space_id, ret_page), page_size,
rw_latch, mtr, init_mtr));
}
5 總結(jié)
innodb的文件結(jié)構(gòu)由自下而上包括page(頁(yè)),extent(簇)幽歼,segment(段)朵锣,tablespace(表空間)等幾個(gè)層次。page是最基本的物理單位甸私,所有page具有相同的頁(yè)首和頁(yè)尾诚些;extent由通常由連續(xù)的64個(gè)page組成,tablespace由一個(gè)個(gè)連續(xù)的extent組成皇型;段是用來(lái)管理物理文件的邏輯單位诬烹,可以向表空間申請(qǐng)分配和釋放page 或 extent,是構(gòu)成索引弃鸦,回滾段的基本元素绞吁;表空間是一個(gè)宏觀概念,當(dāng)innodb_file_per_table為ON時(shí)一個(gè)用戶表對(duì)應(yīng)一個(gè)表空間唬格。