深入理解InnoDB -- 存儲(chǔ)篇

本文分享InnoDB如何規(guī)劃表空間讥巡,如何存儲(chǔ)表空間元信息以及用戶數(shù)據(jù)。

思考一個(gè)問(wèn)題趴樱,如果給你一個(gè)文件馒闷,讓你存儲(chǔ)MySql的數(shù)據(jù),你會(huì)怎么做叁征?

下面是一種比較合理的思路纳账。首先把文件劃分成大小相等的塊(InnoDB中的頁(yè)),每次取一塊使用捺疼。為了管理這些塊信息疏虫,我們也拿出一塊空間,存儲(chǔ)每一塊空間的位置啤呼,偏移量卧秘,以及已經(jīng)使用和剩余未使用的塊(InnoDB中的FSP HEADER PAGE
,文件管理頁(yè))
然后根據(jù)不同的邏輯建立對(duì)應(yīng)的對(duì)象官扣,如索引對(duì)象翅敌,回滾信息對(duì)象(InnoDB中的段),這些對(duì)象從上面分好的塊中申請(qǐng)空間使用惕蹄,并管理屬于自己的塊蚯涮,當(dāng)然,這些對(duì)象信息也需要拿出一塊空間存儲(chǔ)起來(lái)(InnoDB中的INODE PAGE)卖陵。
這就是InnoDB中段和頁(yè)的概念

下面來(lái)明確幾個(gè)核心概念

  • 表空間
    InnoDB將所有數(shù)據(jù)(包括表數(shù)據(jù)遭顶,索引,回滾信息泪蔫,插入緩沖索引頁(yè)棒旗,系統(tǒng)事務(wù)信息,二次寫緩沖)邏輯地放在一個(gè)空間中撩荣,稱為共享表空間铣揉。
    默認(rèn)表空間的存儲(chǔ)文件為data目錄下的ibdata1饶深,初始化為10M。


  • 一個(gè)索引(InnoDB都是B+索引)由兩個(gè)段管理逛拱,葉子節(jié)點(diǎn)段(leaf segment)和非葉子節(jié)點(diǎn)段(non leaf segment)
    回滾數(shù)據(jù)也是通過(guò)段管理粥喜。

  • 區(qū)
    InnoDB申請(qǐng)空間的最小單位,由連續(xù)頁(yè)組成的空間橘券,大小為1MB,保持不變卿吐。
    InnoDB一次從磁盤中申請(qǐng)4~5個(gè)區(qū)旁舰。
  • 頁(yè)
    InnoDB訪問(wèn)的最小單位,默認(rèn)16KB嗡官。一個(gè)區(qū)中一共有64個(gè)連續(xù)的頁(yè)箭窜。
    緩沖池是以頁(yè)為管理單位,每次讀取或刷新一頁(yè)數(shù)據(jù)衍腥。
    參數(shù): innodb_page_size磺樱,可以將頁(yè)大小設(shè)置為4K,8K.

InnoDB將表空間按Page切分婆咸,這些Page主要分為兩類:存儲(chǔ)表空間元信息的管理頁(yè)(如FIL_PAGE_TYPE_FSP_HDR)和存儲(chǔ)表空間用戶數(shù)據(jù)的索引頁(yè)(如FIL_PAGE_INDEX竹捉,F(xiàn)IL_PAGE_INODE)。

FIL Header

所有頁(yè)都有兩個(gè)統(tǒng)一的結(jié)構(gòu)尚骄,F(xiàn)IL Header块差,占據(jù)頁(yè)面的前38個(gè)字節(jié),F(xiàn)IL Trailer倔丈,占據(jù)頁(yè)面末尾8字節(jié)憨闰。

FIL Header結(jié)構(gòu)如下

變量 字節(jié) 描述
FIL_PAGE_SPACE 4 所在表空間ID(space id)
FIL_PAGE_OFFSET 4 該頁(yè)在表空間的偏移量(page no)
FIL_PAGE_PREV 4 前驅(qū)節(jié)點(diǎn)的偏移量(僅對(duì)索引頁(yè)有效)
FIL_PAGE_NEXT 4 后繼節(jié)點(diǎn)的偏移量(僅對(duì)索引頁(yè)有效)
FIL_PAGE_LSN 8 頁(yè)最后刷新到磁盤的LSN
FIL_PAGE_TYPE 2 頁(yè)的類型
FIL_PAGE_FILE_FLUSH_LSN 8 僅在第一個(gè)Page(FSP HEADER PAGE)使用,用來(lái)判斷數(shù)據(jù)庫(kù)是否正常關(guān)閉
FIL_PAGE_SPACE_ID 8 僅在第一個(gè)Page使用需五,保存數(shù)據(jù)庫(kù)關(guān)閉時(shí)歸檔重做日志的編號(hào)

InnoDB中每一個(gè)表空間都會(huì)有一個(gè)唯一的space id鹉动,共享表空間的space id就是0。
每個(gè)頁(yè)都有一個(gè)32位序號(hào)page no宏邮,稱為偏移量泽示,即離表空間初始位置的偏移量。因?yàn)槊總€(gè)頁(yè)大小為16kb蜀铲,所以第0個(gè)頁(yè)的偏移量為0边琉,第一個(gè)頁(yè)的偏移量為16384,以此類推记劝。
通過(guò)space id和page no变姨,InnoDB可以定位任何一個(gè)頁(yè)。

FIL_PAGE_TYPE標(biāo)志頁(yè)的類型厌丑,InnoDB常用頁(yè)類型如下
FIL_PAGE_TYPE_ALLOCATED:該頁(yè)為最新分配
FIL_PAGE_IBUF_BITMAP:Insert Buffer位圖頁(yè)
FIL_PAGE_TYPE_SYS:系統(tǒng)頁(yè)
FIL_PAGE_TYPE_TRX_SYS:事務(wù)系統(tǒng)數(shù)據(jù)頁(yè)
FIL_PAGE_TYPE_FSP_HDR:FSP HEADER PAGE頁(yè)
FIL_PAGE_TYPE_XDES:擴(kuò)展描述頁(yè)
FIL_PAGE_IBUF_FREE_LIST:Insert Buffer空閑列表頁(yè)
FIL_PAGE_UNDO_LOG:Undo Log頁(yè)
FIL_PAGE_INDEX:B+樹葉子節(jié)點(diǎn)頁(yè)
FIL_PAGE_INODE:B+樹索引節(jié)點(diǎn)頁(yè)
FIL_PAGE_TYPE_BLOB:BLOB頁(yè)

FIL Trailer

FIL Trailer是在文件末尾的最后8個(gè)字節(jié)定欧, 低位4個(gè)字節(jié)是用來(lái)表示Page頁(yè)中數(shù)據(jù)的checksum渔呵,最后4字節(jié)和FIL Header中的FIL_PAGE_LSN相同
下面說(shuō)到的頁(yè)都有FIL Header,F(xiàn)IL Trailer砍鸠,不再重復(fù)說(shuō)明扩氢。

現(xiàn)在看一下關(guān)鍵的關(guān)鍵的管理頁(yè)。

FSP HEADER PAGE

表空間第1頁(yè)就是文件管理頁(yè)FSP HEADER PAGE爷辱,存儲(chǔ)表空間關(guān)鍵元數(shù)據(jù)信息录豺。由FSP HEADER、XDES ENTRIES構(gòu)成饭弓。

FSP HEADER

FSP HEADER主要存儲(chǔ)表空間元信息双饥,維護(hù)關(guān)鍵結(jié)構(gòu)分配信息,主要變量如下:

變量 字節(jié) 描述
FSP_SIZE 4 表空間大小弟断,以Page數(shù)量計(jì)算
FSP_FREE_LIMIT 4 當(dāng)前已經(jīng)使用的位置
FSP_FREE 16 空閑區(qū)鏈表
FSP_FREE_FRAG 16 部分可以用碎片區(qū)鏈表
FSP_FULL_FRAG 16 已經(jīng)完全使用的碎片區(qū)鏈表
FSP_SEG_INODES_FULL 16 已經(jīng)完全使用的INODE PAGE鏈表
FSP_SEG_INODES_FREE 16 部分可用的INODE PAGE鏈表

區(qū)具體可以分為區(qū)(extent)和碎片區(qū)(frag extent)
碎片區(qū)是比較特殊的區(qū)咏花,用于分配碎片頁(yè)。

XDES ENTRIES

接下來(lái)是區(qū)描述符XDES ENTRIES阀趴,每個(gè)區(qū)描述符需占用40個(gè)字節(jié)昏翰,用于追蹤64個(gè)頁(yè)的使用狀態(tài)。
每個(gè)FSP HEADER PAGE只能管理256個(gè)區(qū)的信息(也就是16384個(gè)頁(yè))刘急,因此每隔16384個(gè)頁(yè)棚菊,會(huì)有一個(gè)類似FSP HEADER PAGE的Page來(lái)描述隨后的區(qū)信息

XDES ENTRIES主要變量如下

變量 字節(jié) 描述
XDES_FLST_NODE 12 維護(hù)鏈表前后節(jié)點(diǎn)信息
XDES_STATE 4 標(biāo)識(shí)該區(qū)是屬于FSP_FREE,F(xiàn)SP_FRAG_FREE或FSP_FRAG_FREE_FULL或XDES_SEG(某個(gè)段)
XDES_BITMAP 16 標(biāo)識(shí)區(qū)中64個(gè)頁(yè)的使用狀態(tài)

XDES_BITMAP使用位圖方式保存排霉,每個(gè)頁(yè)的使用狀態(tài)占用2位(預(yù)留一位)窍株。

一個(gè)區(qū)可以屬于FSP_FREE,F(xiàn)SP_FRAG_FREE或FSP_FRAG_FREE_FULL或者某一個(gè)段攻柠。區(qū)的分配實(shí)現(xiàn)了一套類似于借還的機(jī)制球订。段向表空間租借區(qū),只有段退還該空間時(shí)瑰钮,該區(qū)才能重新出現(xiàn)在FSP_FREE/FSP_FULL_FRAG/FSP_FULL中冒滩。

INODE PAGE

表空間文件的第3個(gè)page的類型為FIL_PAGE_INODE,管理表空間的段浪谴。

INODE PAGE由SEGMENT INODE組成开睡,每個(gè)SEGMENT INODE為192字節(jié),對(duì)應(yīng)一個(gè)段苟耻。

SEGMENT INODE結(jié)構(gòu)主要變量如下:

變量 字節(jié) 描述
FSEG_FREE 16 未使用的extend鏈表
FSEG_FULL 16 已完全使用的extend鏈表
FSEG_NOT_FULL 16 部分可用的extend鏈表
FSEG_FRAG_ARR[0] 4 碎片頁(yè)數(shù)組首頁(yè)地址
...
FSEG_FRAG_ARR[31] 4 碎片頁(yè)數(shù)組尾頁(yè)地址

為節(jié)省空間篇恒,每個(gè)segment都先從FSP HEADER的FSP_FREE_FRAG中分配32個(gè)碎片頁(yè)(FSEG_FRAG_ARR),當(dāng)這些32個(gè)頁(yè)面不夠使用時(shí)凶杖,再申請(qǐng)區(qū)胁艰。

每個(gè)INODE PAGE默認(rèn)可存儲(chǔ)85個(gè)SEGMENT INODE。每個(gè)索引使用2個(gè)segment,分別用于管理葉子節(jié)點(diǎn)和非葉子節(jié)點(diǎn)腾么。
所以一個(gè)INODE PAGE最多可以保存42個(gè)索引信息(一個(gè)索引使用兩個(gè)段)奈梳。如果表空間有超過(guò)42個(gè)索引,則必須再分配一個(gè)INODE PAGE。INODE PAGE的分配是從碎片區(qū)中申請(qǐng)解虱,但它的位置不是固定的攘须。為了找到索引的INODE ENTRY,InnoDB定義了SEGMENT HEADER殴泰,結(jié)構(gòu)如下

變量 字節(jié) 描述
FSEG_HDR_SPACE 4 INODE PAGE所在表空間ID
FSEG_HDR_PAGE_NO 4 INODE PAGE所在表空間的偏移量
FSEG_HDR_OFFSET 2 INODE ENTRY在頁(yè)的偏移量

對(duì)于用戶表于宙,其索引的Root Page中保存了兩個(gè)SEGMENT HEADER,分別指向葉子節(jié)點(diǎn)的SEGMENT INODE和非葉子節(jié)點(diǎn)的SEGMENT INODE悍汛。

鏈表結(jié)構(gòu)

InnoDB的鏈表都是雙向鏈表限煞,如FSP HEADER中變量FSP_FREE,F(xiàn)SP_FREE_FRAG员凝,F(xiàn)SP_FULL_FRAG,F(xiàn)SP_SEG_INODES_FULL奋献,他們都是鏈表頭結(jié)構(gòu)FLST_BASE_NODE健霹,維護(hù)了鏈表的頭指針和末尾指針,

變量 字節(jié) 描述
FLST_LEN 4 鏈表長(zhǎng)度
FLST_FIRST 6 鏈表首節(jié)點(diǎn)地址
FLST_LAST 6 鏈表尾節(jié)點(diǎn)地址

它們指向的節(jié)點(diǎn)為XDES ENTRIES的XDES_FLST_NODE瓶蚂,每個(gè)節(jié)點(diǎn)的結(jié)構(gòu)體稱為FLST_NODE

變量 字節(jié) 描述
FLST_PREV 6 鏈表前驅(qū)節(jié)點(diǎn)地址
FLST_NEXT 6 鏈表后繼節(jié)點(diǎn)地址

下面是一個(gè)表空間的示意圖糖埋,請(qǐng)理解該圖


第2個(gè)Page是FIL_PAGE_IBUF_BITMAP,主要用于跟蹤隨后的每個(gè)PAGE的change buffer信息窃这,使用4個(gè)bit來(lái)描述每個(gè)page的change buffer信息瞳别。
由于FIL_PAGE_IBUF_BITMAP的空間有限,同樣每隔256個(gè)Extent Page之后杭攻,也會(huì)在XDES PAGE之后創(chuàng)建一個(gè)FIL_PAGE_IBUF_BITMAP祟敛。

其他的表空間元信息Page,如
FSP_TRX_SYS_PAGE_NO兆解,共享表空間第6個(gè)Page馆铁,記錄了InnoDB重要的事務(wù)系統(tǒng)信息。
FSP_DICT_HDR_PAGE_NO锅睛,共享表空間第8個(gè)Page埠巨,存儲(chǔ)了SYS_TABLES,SYS_TABLE_IDS现拒,SYS_COLUMNS辣垒,SYS_INDEXES和SYS_FIELDS等數(shù)據(jù)詞典表的Root Page(b+樹Root節(jié)點(diǎn)所在Page)。
有興趣的同學(xué)可以自行了解

索引組織表

上面說(shuō)了InnoDB通過(guò)索引頁(yè)來(lái)存放行記錄印蔬,那么這些行記錄是怎么組織的呢
(這里說(shuō)的索引頁(yè)勋桶,包括了B+樹葉子節(jié)點(diǎn)頁(yè)FIL_PAGE_INDEX和B+樹索引節(jié)點(diǎn)頁(yè)FIL_PAGE_INODE)

聚集索引

InnoDB中,表都是根據(jù)聚集索引順序組織存放的,這種存儲(chǔ)方式的表稱為索引組織表哥遮。
而InnoDB中主鍵索引使用的是B+索引(通過(guò)B+樹組織的索引)

當(dāng)我們需要打開一張表時(shí)岂丘,需要從表空間的數(shù)據(jù)詞典表中加載元數(shù)據(jù)信息,其中SYS_INDEXES系統(tǒng)表中記錄了用戶表中所有索引Root Page對(duì)應(yīng)的page no眠饮,進(jìn)而找到B+樹Root Page奥帘,就可以對(duì)整個(gè)用戶數(shù)據(jù)B+樹進(jìn)行操作。

B+是為磁盤和其他直接存取輔助設(shè)備設(shè)計(jì)的一種多路平衡查找樹仪召。
看一個(gè)例子


B+樹有以下特點(diǎn)
1寨蹋、B+樹不僅是多叉樹,而且每個(gè)非葉子節(jié)點(diǎn)只存儲(chǔ)鍵值扔茅,不存儲(chǔ)數(shù)據(jù)已旧,這樣每個(gè)非葉子節(jié)點(diǎn)所能保存的鍵值大大增加,可以降低B+的樹深度召娜。
該特性應(yīng)用到索引上运褪,可以使每次加載的節(jié)點(diǎn)包括更多的索引數(shù)據(jù),也可以減少IO操作(每次讀取樹的下一層都需要一次IO)玖瘸。
所以B+索引具有高扇出性秸讹,在數(shù)據(jù)庫(kù)中,B+樹的高度一般都在2~4層雅倒,查找某一個(gè)鍵值的行記錄最多只需要2到4次IO璃诀。

  1. B+樹中,所有數(shù)據(jù)按鍵值的大小順序存放在同一層的葉子節(jié)點(diǎn)上蔑匣,由各葉子節(jié)點(diǎn)指針進(jìn)行連接劣欢。
    每次查找數(shù)據(jù)都需要查找到葉子節(jié)點(diǎn),查找次數(shù)都相同裁良,所以查詢速度很穩(wěn)定凿将。

  2. B+樹所有的葉子節(jié)點(diǎn)數(shù)據(jù)構(gòu)成了一個(gè)有序鏈表,在查詢大小區(qū)間的數(shù)據(jù)時(shí)候非常方便价脾。
    如上面例子中的B+樹丸相,如果要查詢[22,89]范圍數(shù)據(jù),再需要找到鍵值22彼棍,再遍歷到數(shù)據(jù)鍵值89就可以了灭忠。
    而遍歷所有數(shù)據(jù),只需要遍歷所有的葉子節(jié)點(diǎn)即可座硕,而不需要遍歷每一層數(shù)據(jù)弛作,這有利于數(shù)據(jù)庫(kù)做全表掃描。

注意:B+樹所有的葉子節(jié)點(diǎn)數(shù)據(jù)構(gòu)成了一個(gè)有序鏈表华匾,這個(gè)是邏輯上的有序映琳,而非物理存儲(chǔ)是順序(維護(hù)成本過(guò)高)机隙。
InnoDB中,Page的FIL Header維護(hù)了上下Page的偏移量萨西,組成雙向鏈表有鹿,而Page中行記錄的記錄頭中維護(hù)了下一行記錄的位置,組成單向鏈表谎脯。

B+樹的查找
類似于二叉查找樹葱跋。起始于根節(jié)點(diǎn),自頂向下遍歷樹源梭,根據(jù)目標(biāo)值與鍵值比較結(jié)果向下查找對(duì)應(yīng)子樹娱俺。
但B+的數(shù)據(jù)都存儲(chǔ)在葉子節(jié)點(diǎn),所以就算某個(gè)非葉子節(jié)點(diǎn)的鍵值與所查的關(guān)鍵字相等時(shí)废麻,并不停止查找荠卷,而是繼續(xù)沿著這個(gè)節(jié)點(diǎn)左邊的指針向下,一直查到該關(guān)鍵字所在的葉子節(jié)點(diǎn)為止烛愧。

B+樹的平衡
對(duì)于插入和刪除操作油宜,B+通過(guò)分裂和合并節(jié)點(diǎn)維持平衡(類型紅黑樹的旋轉(zhuǎn)),
InnoDb中B+樹的鍵值和數(shù)據(jù)都存放在Page中怜姿,因此Page也需要合并和分裂验庙,有興趣的同學(xué)可以自行了解。

輔助索引

InnoDB中聚集索引和輔助索引都是B+索引社牲。但輔助索引葉子節(jié)點(diǎn)的數(shù)據(jù)不是存儲(chǔ)實(shí)際的數(shù)據(jù),而是主鍵的值悴了。要想拿到實(shí)際的數(shù)據(jù)需要再通過(guò)主鍵索引找到對(duì)應(yīng)的行記錄然后才能拿到實(shí)際的數(shù)據(jù)搏恤,這個(gè)過(guò)程稱為回表。
如果查詢語(yǔ)句可以從輔助索引(包括聯(lián)合索引)中獲取到所有需要的列湃交,這時(shí)不需要再通過(guò)主鍵索引找到對(duì)應(yīng)的行記錄熟空,這種情況稱為覆蓋索引。

聯(lián)合索引

聯(lián)合索引也是B+索引搞莺。
聯(lián)合索引中列的順序很重要息罗。
InnoDB首先根據(jù)聯(lián)合索引中最左邊的、也就是第一列進(jìn)行排序才沧,在第一列排序的基礎(chǔ)上迈喉,再對(duì)聯(lián)合索引中后面的第二列進(jìn)行排序,依此類推温圆。
所以如果想使用聯(lián)合索引的第n列挨摸,查詢條件中必須包括聯(lián)合索引前面的第1列到第n-1列的查詢信息。

(group, score)岁歉,可能出現(xiàn)以下排序(1, 46), (1,58), (2,23), (2,96), (3,25), (3,67)得运。
如果要使用該索引的score,查詢條件中必須包含group,如where group = 2 and score = 96熔掺,InnoDB通過(guò)group查詢后饱搏,再通過(guò)score查詢。
這個(gè)規(guī)則稱為最左前綴匹配原則置逻。

頁(yè)結(jié)構(gòu)

下面看一下InnoDB索引頁(yè)如何保持用戶數(shù)據(jù)推沸,索引頁(yè)由以下部分組成

變量 字節(jié) 描述
Page Header 56 頁(yè)頭,記錄頁(yè)的一些狀態(tài)信息
Infimun/Supremum Records 26 系統(tǒng)記錄
User Records 不確定 用戶記錄诽偷,即行記錄
Free Space 不確定 空閑空間
Page Directory 不確定 頁(yè)目錄

Page Header

變量 字節(jié) 描述
PAGE_N_DIR_SLOTS 2 page directory中槽的數(shù)量
PAGE_HEAP_TOP 2 堆中空閑空間的偏移量
PAGE_N_HEAP 2 記錄數(shù)據(jù)數(shù)量坤学,包含用戶記錄,系統(tǒng)記錄以及標(biāo)記刪除的記錄
PAGE_FREE 2 刪除記錄的鏈表
PAGE_GARBAGE 2 已標(biāo)記刪除記錄數(shù)量
PAGE_N_RECS 2 用戶記錄數(shù)量报慕,不包含系統(tǒng)記錄以及標(biāo)記刪除的記錄
PAGE_MAX_TRX_ID 8 最近一次修改該P(yáng)age記錄的事務(wù)ID
PAGE_LEVEL 2 當(dāng)前頁(yè)在索引樹的位置
PAGE_INDEX_ID 8 索引id深浮,表示當(dāng)前頁(yè)屬于那個(gè)索引
PAGE_BTR_SEG_LEAF 10 B+樹葉子節(jié)點(diǎn)所在段的segment header,僅在B+樹的Root Page中定義
PAGE_BTR_SEG_TOP 10 B+樹非葉子節(jié)點(diǎn)所在段的segment header眠冈,僅在B+樹的Root Page中定義

PAGE_LAST_INSERT飞苇,PAGE_DIRECTION,PAGE_N_DIRECTION等變量并未在表中列出蜗顽,他們用于進(jìn)行頁(yè)的分裂操作布卡。

當(dāng)記錄被刪除(不僅是將記錄的deleted_flag設(shè)置為1,而是徹底刪除)雇盖,會(huì)放到PAGE_FREE鏈表中(鏈表通過(guò)記錄頭信息next_record串聯(lián))忿等,如果這個(gè)頁(yè)上有記錄要插入,會(huì)先檢查PAGE_FREE鏈表空間是否滿足崔挖,如果空間滿足贸街,直接從PAGE_FREE鏈表空間分配,如果空間不夠狸相,再?gòu)目臻e地址(PAGE_HEAP_TOP)分配薛匪。
注意:檢查PAGE_FREE鏈表空間時(shí),僅檢查第一個(gè)節(jié)點(diǎn)的可用空間脓鹃,不會(huì)通過(guò)next_record進(jìn)行遍歷逸尖。

頁(yè)記錄是根據(jù)主鍵順序排序的,這個(gè)排序是邏輯上的瘸右,而非物理上的(開銷過(guò)大)娇跟。當(dāng)頁(yè)空間不足時(shí),會(huì)調(diào)用函數(shù)btr_page_reorganize_low進(jìn)行頁(yè)的重新組織太颤,即根據(jù)頁(yè)中記錄主鍵的順序重新進(jìn)行整理逞频,這樣就能整理出碎片的空間。若還是空間不足栋齿,則進(jìn)行分裂操作苗胀。

Infimun和Supremum Records
系統(tǒng)虛擬的記錄襟诸,Infimun表示比任何主鍵值都小的值,Supremum表示比任何可能的值都大的值基协。

User Records
行記錄以鏈表的形式存放在 User Records 中歌亲,行記錄格式中的記錄頭中的 next_record 存放著下一條記錄的地址

Free Space
隨著記錄越來(lái)越多衷旅,F(xiàn)ree Space空間越來(lái)越小救恨,User Records空間越來(lái)越大。當(dāng)Free Space的全部空間都被分配完了猬腰,這個(gè)頁(yè)也就使用完了杂穷,需要申請(qǐng)新的頁(yè)悍缠。

Page Directory
B+索引本身不能定位具體的一條記錄,只能找到該記錄所在的頁(yè)耐量。
InnoDB將頁(yè)載入到內(nèi)存后飞蚓,可以遍歷頁(yè)所有的記錄找到目標(biāo)記錄,但這樣做太慢了廊蜒。
(Page的記錄非物理順序存儲(chǔ)趴拧,無(wú)法通過(guò)物理地址二分查詢)

InnoDB將頁(yè)中數(shù)據(jù)進(jìn)行分組,將每個(gè)組最后一條數(shù)據(jù)的偏移量按順序存儲(chǔ)起來(lái)山叮,組成目錄著榴。
每個(gè)偏移量也被稱為一個(gè)槽(Slot,兩個(gè)字節(jié))屁倔。這些偏移量都會(huì)被存儲(chǔ)到靠近頁(yè)的尾部的地方脑又,被稱為Page Directory。
這樣InnoDB可以通過(guò)Page Directory進(jìn)行二叉查找定位目標(biāo)所在分組锐借,再遍歷該組數(shù)據(jù)就可以问麸。

每個(gè)槽可以包括4~8條記錄,每個(gè)記錄的記錄頭中n_owned變量瞎饲,維護(hù)該記錄所在槽的記錄數(shù)量。
(例外炼绘,第1個(gè)槽僅包含一個(gè)1記錄嗅战,即Infimun,最后一個(gè)槽可包含1~8個(gè)記錄)

行格式

上面說(shuō)了InnoDB索引頁(yè)如何保存用戶數(shù)據(jù)俺亮,即表的行記錄驮捍,下面看看每一行的存儲(chǔ)格式

InnodB中行記錄存儲(chǔ)方法有Compress,Redundant脚曾,Compressed东且,Dynamic。
Redundant行格式是MySql5.0之前使用的本讥,現(xiàn)在基本不會(huì)再使用珊泳,這里就不介紹了鲁冯。

compact格式下,一行記錄依次為以下內(nèi)容:
變長(zhǎng)字段長(zhǎng)度列表色查,NULL標(biāo)志位薯演,記錄頭信息,rowID秧了,TransactionID跨扮,RollPointer,列1數(shù)據(jù)验毡,列2數(shù)據(jù)衡创,...
其中rowID,TransactionID晶通,RollPointer由InnoDB生成璃氢,
注意,如果表中已經(jīng)指定主鍵录择,則不生成rowID拔莱。
(TransactionID,RollPointer用于實(shí)現(xiàn)MVCC功能隘竭,將在事務(wù)篇解析)

變長(zhǎng)字段長(zhǎng)度列表
若列的長(zhǎng)度小于255字節(jié)塘秦,用1字節(jié)表示
若列的長(zhǎng)度大于255字節(jié),用2字節(jié)表示(varchar最大限制為65535)

NULL標(biāo)志位
占用字節(jié)為(可為NULL的列數(shù)量/8)向上取整,字節(jié)中哪一位為1动看,表示該行數(shù)據(jù)對(duì)應(yīng)列為NULL值

注意尊剔,變長(zhǎng)字段長(zhǎng)度列表和NULL標(biāo)志位都是是按列定義的倒敘保存的,他們都是可選的菱皆,如果表中沒有變長(zhǎng)字段和允許NULL值的字段须误,那么這兩個(gè)都是占用0字節(jié)長(zhǎng)度

char(N)中的N指定的是字符,UTF-8下CHAR(10)類型的列仇轻,最小可以存儲(chǔ)10字節(jié)的字符京痢,最大可以存儲(chǔ)30字節(jié)的字符。
所以對(duì)于多字節(jié)的字符篷店,char類型在InnoDB存儲(chǔ)引擎內(nèi)部被視為變長(zhǎng)字符類型祭椰。也意味著這些CHAR數(shù)據(jù)類型的長(zhǎng)度會(huì)記錄在變長(zhǎng)長(zhǎng)度列表中。

記錄頭信息
固定5字節(jié)疲陕,結(jié)構(gòu)如下

變量 字節(jié) 描述
() 2 預(yù)留位
deleted_flag 1 該行是否已被刪除
min_rec_flag 1 如果該行記錄是預(yù)定義為最小的記錄方淤,為1
n_owned 4 該記錄所在Slot擁有的記錄數(shù)
heap_no 13 索引堆中該條記錄的索引號(hào)
record_type 3 記錄類型,000(普通)蹄殃,001(B+Tree節(jié)點(diǎn)指針)携茂,010(Infimum),011(Supremum)
next_record 16 頁(yè)中下一條記錄的相對(duì)位置

看一例子

create table mytest (
    t1 varchar(10),
    t2 varchar(10),
    t3 char(10),
    t4 varchar(10)
) engine=INNODB charset=LATIN1 ROW_format=compact;
insert into mytest3 values('d','11',NULL,'fff');

使用vim打開ibd文件诅岩,在“命令”模式中輸入“:%!xxd”命令讳苦,將文本轉(zhuǎn)換為16進(jìn)制带膜,找到supremum字符串,后面的就是列數(shù)據(jù)了医吊。
(ibd中可能有多個(gè)頁(yè)钱慢,可以依照行的實(shí)際數(shù)據(jù)判斷哪個(gè)是自己要找的數(shù)據(jù)。)

00010070: 7375 7072 656d 756d 0302 0104 0000 10ff  supremum........
00010080: ef00 0000 0002 0100 0000 0010 2281 0000  ............"...
00010090: 010a 0110 6431 3166 6666 0000 0000 0000  ....d11fff......

解析如下

03 02 01   // 變長(zhǎng)字段長(zhǎng)度列表卿堂, d列-03束莫,c列-null,不記錄  b列-02  a列-01
04  // null標(biāo)準(zhǔn)位, 二進(jìn)制-00000100,逆序草描,d,c-null,b,a
00 00 10 ff ef  // Record Header览绿,固定5字節(jié) 
00 0000 0002 01  // RowID ,InnoDB自動(dòng)創(chuàng)建穗慕,6字節(jié) 
00 0000 0010 22  // TransactionID
81 0000 010a 0110  // Roll Pointer
64  // 列1數(shù)據(jù)  'a'
31 31  // 列2數(shù)據(jù) '11'
66 6666  // 列4數(shù)據(jù)  'fff'

行溢出
對(duì)于占用字節(jié)數(shù)非常大的列饿敲,在記錄的真實(shí)數(shù)據(jù)中只會(huì)存儲(chǔ)一小部分?jǐn)?shù)據(jù)(768個(gè)字節(jié)),剩余的數(shù)據(jù)分散在其他溢出頁(yè)中(BLOG類型的頁(yè))逛绵,記錄的真實(shí)數(shù)據(jù)中記錄這些頁(yè)的地址怀各,以便找到他們。
注意:行溢出與列定義的類型無(wú)關(guān)术浪。如果varchar過(guò)長(zhǎng)會(huì)發(fā)生行溢出瓢对,而text,blog不夠長(zhǎng)則不會(huì)發(fā)生行溢出胰苏。

InnoDB 1.0.x引入新的行格式硕蛹,以前支持的Compress 和Redundant稱為Antelope文件格式,新的文件格式為Barracuda文件格式硕并,有兩個(gè)行記錄格式:Compressed和Dynamic法焰。他們是Compact的變種形式。他們基本沒什么本質(zhì)上的區(qū)別倔毙,唯一的區(qū)別就是對(duì)于行溢出的處理不同埃仪。Compressed在數(shù)據(jù)頁(yè)只存儲(chǔ)一個(gè)指向溢出頁(yè)的地址,所有的實(shí)際數(shù)據(jù)都存放在溢出頁(yè)中陕赃。
而Compressed還可以是zlib算法對(duì)行數(shù)據(jù)進(jìn)行壓縮卵蛉,因此對(duì)于BLOB,TEXT凯正,VARCHAR這類大長(zhǎng)度類型的數(shù)據(jù)能夠非常有效的存儲(chǔ)毙玻。

參考文檔:
《MySQL技術(shù)內(nèi)幕InnoDB存儲(chǔ)引擎》
《MYSQL內(nèi)核:INNODB存儲(chǔ)引擎》
Innodb表空間
深度 | 解析InnoDB引擎
Chapter 22 InnoDB Storage ngine
InnoDB 文件系統(tǒng)之文件物理結(jié)構(gòu)

如果您覺得本文不錯(cuò)豌蟋,歡迎關(guān)注我的微信公眾號(hào)廊散,您的關(guān)注是我堅(jiān)持的動(dòng)力!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梧疲,一起剝皮案震驚了整個(gè)濱河市允睹,隨后出現(xiàn)的幾起案子运准,更是在濱河造成了極大的恐慌,老刑警劉巖缭受,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胁澳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡米者,警方通過(guò)查閱死者的電腦和手機(jī)韭畸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蔓搞,“玉大人胰丁,你說(shuō)我怎么就攤上這事∥狗郑” “怎么了锦庸?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蒲祈。 經(jīng)常有香客問(wèn)我甘萧,道長(zhǎng),這世上最難降的妖魔是什么梆掸? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任扬卷,我火速辦了婚禮,結(jié)果婚禮上沥潭,老公的妹妹穿的比我還像新娘邀泉。我一直安慰自己,他們只是感情好钝鸽,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布汇恤。 她就那樣靜靜地躺著,像睡著了一般拔恰。 火紅的嫁衣襯著肌膚如雪因谎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天颜懊,我揣著相機(jī)與錄音财岔,去河邊找鬼。 笑死河爹,一個(gè)胖子當(dāng)著我的面吹牛匠璧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咸这,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼夷恍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了媳维?” 一聲冷哼從身側(cè)響起酿雪,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤遏暴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后指黎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朋凉,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年醋安,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杂彭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吓揪,死狀恐怖盖灸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情磺芭,我是刑警寧澤赁炎,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站钾腺,受9級(jí)特大地震影響徙垫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜放棒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一姻报、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧间螟,春花似錦吴旋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至摩泪,卻和暖如春笆焰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背见坑。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工嚷掠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荞驴。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓不皆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親熊楼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霹娄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359