InnoDB邏輯存儲結(jié)構(gòu)

簡介

image.png

從InnoDB邏輯存儲結(jié)構(gòu)來看,InnoDB所有數(shù)據(jù)都存放到在一個(gè)空間中,稱之為表空間。如圖所示,表空間由段、區(qū)、頁組成。

表空間

表空間可以看做是InnoDB存儲引擎邏輯結(jié)構(gòu)的最外層。之前的文章Mysql——InnoDB存儲引擎架構(gòu)就已經(jīng)介紹過了,表空間分為系統(tǒng)表空間、獨(dú)立表空間、常規(guī)表空間、undo獨(dú)立表空間、共享臨時(shí)表空間。

表空間是由各個(gè)段組成的,常見的段有數(shù)據(jù)段、索引段、回滾段等。

  • 數(shù)據(jù)段:B+樹的葉子節(jié)點(diǎn);
  • 索引段:B+樹的中間節(jié)點(diǎn);
  • 回滾段:記錄undo log。

區(qū)

段是由各個(gè)區(qū)組成的,而區(qū)是由多個(gè)頁組成的,在任何情況下每個(gè)區(qū)的大小都為1M。為了保證區(qū)中頁的連續(xù)性,InnoDB存儲引擎申請空間是按區(qū)為單位申請(一次從磁盤申請4~5個(gè)區(qū))。默認(rèn)的情況下,InnoDB中頁的大小=16KB,即一個(gè)區(qū)有64個(gè)連續(xù)頁。

當(dāng)然Mysql 5.7中InnoDB存儲引擎時(shí)支持頁的壓縮,每個(gè)頁的大小可能是2K、4K、8K、16K,所以每個(gè)區(qū)對應(yīng)頁的數(shù)量變成64~512之間。

考慮這樣的一個(gè)問題,在用戶啟用了參數(shù)innodb_file_per_table后,創(chuàng)建的表默認(rèn)大小為96K,上面說到每個(gè)區(qū)的是連續(xù)的64個(gè)頁,創(chuàng)建表的時(shí)候應(yīng)該是1M才對?

其實(shí)是每個(gè)段開始的時(shí)候,會先用32個(gè)頁大小的碎片頁來存放數(shù)據(jù)(也就是說創(chuàng)建表的時(shí)候默認(rèn)會用到6個(gè)碎片頁),當(dāng)著32個(gè)碎片頁用完的時(shí)候才會申請連續(xù)的64個(gè)頁。對于一些小表來說,可以再開始的時(shí)候申請較少的空間,節(jié)省磁盤空間。

頁是InnoDB磁盤管理的最小單位,默認(rèn)的情況下,InnoDB中頁的大小=16KB,數(shù)據(jù)庫一開始可以通過innodb_page_size將頁的大小設(shè)置為4K、8K、16K,但是一旦數(shù)據(jù)庫表被創(chuàng)建了,后面就不能再修改了。
常見的頁類型有:

  • 索引、數(shù)據(jù)頁(B+Tree Node);
  • undo頁(undo log Page);
  • 系統(tǒng)頁(System Page);
  • 事務(wù)數(shù)據(jù)頁(Transaction system Page);
  • 插入緩沖位圖頁(Insert Buffer Bitmap);
  • 插入緩沖空閑列表頁(Insert Buffer Free List);
  • 未壓縮的二進(jìn)制大對象頁(Uncompressed BLOB Page);
  • 壓縮的二進(jìn)制大對象頁(Compressed BLOB Page)。

索引、數(shù)據(jù)頁結(jié)構(gòu)

即InnoDB存儲引擎B+樹的節(jié)點(diǎn)。InnoDB的數(shù)據(jù)頁由以下7個(gè)部分組成:

  • File Header(文件頭):38字節(jié),用來記錄頁的頭部信息,由8個(gè)部分組成。
名稱 空間(字節(jié)) 說明
FIL_PAGE_SPACE_OR_CHKSUM 4 代表該頁的checksum值
FIL_PAGE_OFFSET 4 表空間頁的偏移值,也就是該頁在某個(gè)表空間所有頁的的位置
FIL_PAGE_PREV 4 當(dāng)前頁的上一個(gè)頁的位置
FIL_PAGE_NEXT 4 當(dāng)前頁的下一個(gè)頁的位置
FIL_PAGE_LSN 8 該頁最后一次被修改的日志序列位置LSN
FIL_PAGE_TYPE 2 頁的類型
FIL_PAGE_FILE_FLUSH_LSN 8 “文件已經(jīng)被刷新到磁盤上,至少到這個(gè)lsn”,僅在文件的第一頁有效
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 頁屬于哪個(gè)表空間

頁的類型主要有以下幾種:

名稱 16進(jìn)制 說明
FIL_PAGE_TYPE_ALLOCATED 0x000 該頁為最新分配
FIL_PAGE_UNDO_LOG 0x002 Undo log頁
FIL_PAGE_INODE 0x003 索引節(jié)點(diǎn)
FIL_PAGE_IBUF_FREE_LIST 0x004 插入緩沖空閑列表
FIL_PAGE_IBUF_BITMAP 0x005 插入緩沖位圖
FIL_PAGE_TYPE_SYS 0x006 系統(tǒng)頁
FIL_PAGE_TYPE_TRX_SYS 0x007 事務(wù)系統(tǒng)數(shù)據(jù)
FIL_PAGE_TYPE_FSP_HDR 0x008 File Space Header
FIL_PAGE_TYPE_XDES 0x009 擴(kuò)展描述頁
FIL_PAGE_TYPE_BLOB 0x00A BLOB頁
FIL_PAGE_INDEX 0x45BF B+樹葉節(jié)點(diǎn)
  • Page Header(頁頭):56字節(jié),用來記錄數(shù)據(jù)頁的狀態(tài)信息,由14個(gè)部分組成。
名稱 空間(字節(jié)) 說明
PAGE_N_DIR_SLOTS 2 表示頁目錄的插槽數(shù)
PAGE_HEAP_TOP 2 堆中第一個(gè)記錄的指針,記錄在頁中是根據(jù)堆的形式存放的
PAGE_N_HEAP 2 堆中的記錄數(shù),第15位表示行記錄格式
PAGE_FREE 2 指向可重用空間的首指針
PAGE_GARBAGE 2 已刪除記錄的字節(jié)數(shù)
PAGE_LAST_INSERT 2 最后插入記錄的位置
PAGE_DIRECTION 2 最后插入的方向:
PAGE_LEFT(0x01)
PAGE_RIGHT(0x02)
PAGE_SMAE_REC(0x03)
PAGE_SMAE_PAGE(0x04)
PAGE_NO_DIRECTION(0x05)
PAGE_N_DIRECTION 2 一個(gè)方向連續(xù)插入的記錄數(shù)
PAGE_N_RECS 2 該頁的記錄數(shù)
PAGE_MAX_TRX_ID 8 修改當(dāng)前頁最大的事務(wù)ID,注意該值僅在二級索引定義
PAGE_LEVEL 2 當(dāng)前頁在索引樹的層數(shù)
PAGE_INDEX_ID 8 索引ID,代表該頁屬于哪個(gè)索引
PAGE_BTR_SEG_LEAF 10 B+樹數(shù)據(jù)頁非葉子節(jié)點(diǎn)所在段的segment header
PAGE_BTR_SEG_TOP 10 B+樹數(shù)據(jù)頁所在段的segment header
  • Infimun和Supremum Records:屬于InnoDB存儲引擎每個(gè)頁中的兩個(gè)虛擬行記錄,用來限定記錄的邊界。Infimun:下界,表示比任何主鍵值還要小的值;Supremum:上界,表示比任何主鍵值還要大的值;這兩個(gè)值在頁創(chuàng)建的時(shí)候被建立,在任何情況下都不會被刪除。
  • User Records(行記錄):用戶實(shí)際存儲的行記錄。
  • Free Space(空閑空間):頁的空閑空間。
  • Page Directory(頁目錄):存放記錄的相對位置的指針,這些指針可以稱之為Slots(槽)。在InnoDB中并不是每個(gè)記錄都擁有一個(gè)Slot,而是一個(gè)稀疏目錄,也就是說一個(gè)Slot可能包含多個(gè)記錄。在Slots中記錄是按照索引鍵值順序存放的,這樣可以利用二叉查找樹迅速查找到對應(yīng)的Slot。用戶查找數(shù)據(jù)的時(shí)候先通過B+樹找到對應(yīng)的頁,然后將頁加載到內(nèi)存,然后通過Page Directory進(jìn)行二叉查找,找到對應(yīng)的Slot,然后通過該Slot指向的記錄進(jìn)行next_record繼續(xù)查找相對應(yīng)的記錄。
image
  • File Trailer(文件結(jié)尾信息):8字節(jié),為了檢測頁是否已經(jīng)完整寫入磁盤(如可能發(fā)生寫入過程中磁盤損壞,機(jī)器關(guān)機(jī)等)。
名稱 空間(字節(jié)) 說明
FILE_PAGE_END_LSN 8 前4字節(jié)表示頁的checksum值;后4字節(jié)與File Header的FIL_PAGE_LSN相同。將這兩個(gè)值與File Header的FIL_PAGE_SPACE_OR_CHKSUM和FIL_PAGE_LSN進(jìn)行比較,確認(rèn)是否一致,以此來保證頁的完整性。

在InnoDB存儲引擎中,表都是根據(jù)主鍵順序組織存放的,即按照主鍵作為索引鍵值,因此InnoDB存儲引擎中的表實(shí)際上就是顆B+Tree,樹的節(jié)點(diǎn)存放的是索引、數(shù)據(jù)頁,其中葉子節(jié)點(diǎn)存放的是數(shù)據(jù),非葉子節(jié)點(diǎn)存放的是索引,每個(gè)葉子節(jié)點(diǎn)包含多行數(shù)據(jù)。
InnoDB存儲引擎對每個(gè)頁數(shù)據(jù)的存放是有硬性要求的,最多允許16KB/2 - 200 = 7992行數(shù)據(jù),最少需要2行數(shù)據(jù)。

行記錄格式

InnoDB存儲引擎提供了Redundant、Compact(Mysql5.1 默認(rèn))、Dynamic(Mysql5.7 默認(rèn))、Compressed四種格式來存放行記錄,可以使用show table status;查看表中的行記錄格式。

image

Compact行記錄格式

image

上圖展示了Compact行記錄格式,主要有以下幾部分:

  • 變長字段列表:存儲變長字段的長度,按照列的順序逆序來存儲;如果列的長度<255,用1個(gè)字節(jié)表示;否則使用2個(gè)字節(jié)表示(注意,VARCHAR最大長度為65535);

  • NULL標(biāo)志位:表示改行數(shù)據(jù)是否有空值,有則用1表示,該部分占用1個(gè)字節(jié);

  • 記錄頭信息:固定5個(gè)字節(jié)。存儲格式如下:

    名稱 空間(bit) 說明
    () 1 未知
    () 1 未知
    delete_flag 1 該行是否被刪除
    min_rec_flag 1 如果等于1,該記錄是預(yù)先被定義為最小的記錄
    n_owned 4 該記錄擁有的記錄數(shù)
    heap_no 13 索引堆中該條記錄的排序記錄
    record_type 3 記錄類型:
    000表示普通
    001表示B+樹節(jié)點(diǎn)指針
    010表示Infimum
    011表示Supremun
    1XX表示保留
    next_record 16 頁中下一條記錄的相對位置
  • 實(shí)際存儲每個(gè)列的數(shù)據(jù);

需要注意的是如果某個(gè)列的值為NULL,則不占任何空間(除了占有NULL標(biāo)志位,為1)。

每行數(shù)據(jù)除了用戶定義的以外,還有兩個(gè)隱藏列:事務(wù)ID列(6個(gè)字節(jié))和回滾指針列(7個(gè)字節(jié))。
如果表沒有定義主鍵,每行還會增加一個(gè)rowid列。

Redundant行記錄格式

image

Redundant是Mysql5.0版本之前的行記錄格式。上圖展示了Redundant行記錄格式,主要有以下幾部分:

  • 字段長度偏移列表:存儲所有字段長度的偏移量,按照列的順序逆序來存儲(例如:23 20 16 14 13 0c 06字段長度偏移列表,06代表第一列的長度=6,0c-06=第二列的長度,13-0c=第三列的長度,以此類推);如果列的長度<255,用1個(gè)字節(jié)表示;否則使用2個(gè)字節(jié)表示(注意,VARCHAR最大長度為65535);

  • 記錄頭信息:占用6個(gè)字節(jié),存儲格式如下:

    名稱 空間(bit) 說明
    () 1 未知
    () 1 未知
    delete_flag 1 該行是否被刪除
    min_rec_flag 1 如果等于1,該記錄是預(yù)先被定義為最小的記錄
    n_owned 4 該記錄擁有的記錄數(shù)
    heap_no 13 索引堆中該條記錄的排序記錄
    n_fileds 10 記錄中列的數(shù)量
    1byte_offs_flag 1 偏移列表為1個(gè)字節(jié)還是2個(gè)字節(jié)
    next_record 16 頁中下一條記錄的相對位置
  • 實(shí)際存儲每個(gè)列的數(shù)據(jù);

①考慮這樣的一個(gè)問題,之前說到頁的大小默認(rèn)16K(16384個(gè)字節(jié)),并且一個(gè)數(shù)據(jù)頁中至少要包含2行記錄,然而Mysql Varchar最大存儲字節(jié)65535個(gè)字節(jié)(當(dāng)然還有其他可變長大字段,TEXT、BLOB) > 頁的大小,兩者之間是否會互相矛盾?

事實(shí)上會互相矛盾,但mysql引入行溢出的機(jī)制來解決這個(gè)問題。

image

Compact和Redundant行記錄格式采用上圖存儲方式,數(shù)據(jù)頁只保留前768個(gè)字節(jié)的前綴數(shù)據(jù),之后是偏移量指針,指向行溢出頁。
②什么時(shí)候會觸發(fā)行溢出呢?
剛剛講到一個(gè)數(shù)據(jù)頁至少要包含2行記錄,因此,如果當(dāng)前頁只能存放一個(gè)記錄的時(shí)候,InnoDB存儲引擎就會自動(dòng)把行數(shù)據(jù)存放到溢出頁中。
③對于TEXT或者BLOB,是不是總是存放到溢出頁中?
是否存放到溢出頁中,都要遵循上面第②點(diǎn)。
④Varchar最大存儲字節(jié)65535個(gè)字節(jié),是指單個(gè)列的長度么?
不是的,是指一行記錄的所有VARCHAR列長度的總和不能超過65535個(gè)字節(jié)。
⑤Varchar(n)或者Char(n)中的n指的是字節(jié)個(gè)數(shù)還是字符長度?
指的是字符長度。不同的字符集對列的長度會有影響,比如定義Varchar的最大長度,lantin則可以定義Varchar(65535),如果是utf-8(3個(gè)字節(jié))則只能定義Varchar(65535/3)。對于char(n)來說,不同的字符集長度是不固定的,InnoDB存儲引擎在內(nèi)部會將其視為變長字符類型。

Dynamic行記錄格式

InnoDB 1.0X版本開始引入新的文件格式,以前支持Compact和Redundant格式稱為Antelope文件格式,新的格式有:Compressed和Dynamic,稱為Baracuda文件格式。

Dynamic行格式提供與Compact行格式相同的存儲特性,但對于長可變長度列值(對于VARCHAR,VARBINARY和BLOB和TEXT類型)行溢出采用了完全的行溢出方式。如圖所示,在數(shù)據(jù)頁的某行記錄里的某個(gè)大可變長度列值只存放20個(gè)字節(jié)的指針,實(shí)際的數(shù)據(jù)都存放到外部溢出頁中。
image

Compressed行記錄格式

Compressed行格式提供與Dynamic行格式相同的存儲特性和功能,但增加了對表和索引數(shù)據(jù)壓縮的支持(采用zlib算法壓縮)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容