InnoDB行格式

InnoDB的記錄按行存儲(chǔ)在數(shù)據(jù)頁(yè)中。記錄在數(shù)據(jù)頁(yè)種的排布在《InnoDB頁(yè)面結(jié)構(gòu)》中已述及,本文重點(diǎn)介紹InnoDB的記錄格式。

1 行格式總覽

InnoDB規(guī)劃了26種行格式,分別對(duì)應(yīng)26種動(dòng)物,首字母由A至Z:Antelope, Barracuda, Cheetah, Dragon, Elk, Fox, Gazelle, Hornet, Impala, Jaguar, Kangaroo, Leopard, Moose, Nautilus, Ocelot, Porpoise, Quail, Rabbit, Shark, Tiger, Urchin, Viper, Whale, Xenops, Yak, Zebra。目前InnoDB支持的行格式只有Antelope, Barracuda。而Antelope又具體細(xì)分為RedundantCompact,Barracuda也具體細(xì)分為DynamicCompressed。創(chuàng)建InnoDB表時(shí),可以通過(guò) ROW_FORMAT=XXX子句指定行格式,例如:

Create Table t (a int, b varchar(1000) not null, c char(100), d varchar(100)) CHARSET=utf8mb3 ROW_FORMAT = COMPACT;

Redundant是MySQL 5.0之前的行格式,它存儲(chǔ)的記錄是非緊湊類(lèi)型的,比較占用磁盤(pán)空間。同樣的頁(yè)面中存儲(chǔ)的記錄行更少,索引的效率較低。目前已很少使用。CompactDynamicCompressed三種行格式結(jié)構(gòu)比較相似。由于MySQL 5.7和8.0默認(rèn)的行格式為Dynamic,下面將展開(kāi)介紹Dynamic行格式。

2 Dynamic格式

Dynamic行格式的級(jí)別結(jié)構(gòu)如下:

變長(zhǎng)字段長(zhǎng)度列表 NULL值列表 記錄頭信息 系統(tǒng)列 Field 1 ... Field N

2.1 變長(zhǎng)字段長(zhǎng)度列表

對(duì)于Varchar、Text、Blob等這類(lèi)變長(zhǎng)的字段,其存儲(chǔ)長(zhǎng)度是變長(zhǎng)的。即使對(duì)于長(zhǎng)度相同的字段,例如CHAR(10),雖然其存儲(chǔ)的字符是固定10個(gè),用戶輸入的字符不足10個(gè)也將補(bǔ)齊至10個(gè),但如果字符集是可以使用1-3個(gè)字節(jié)存儲(chǔ)字符的utf8mb3,其存儲(chǔ)字符的字節(jié)數(shù)也是變長(zhǎng)的。InnoDB為了能準(zhǔn)確劃分、解析不同的字段,在每條記錄的第一步部分會(huì)記錄所有變長(zhǎng)字段的長(zhǎng)度。注意,例如Int固定長(zhǎng)度和為空的變長(zhǎng)字段的長(zhǎng)度是不會(huì)記錄于此的。

具體而言,每個(gè)字段的長(zhǎng)度使用1-2字節(jié)記錄。MySQL對(duì)字段由65535長(zhǎng)度的限制也源自于此,因?yàn)?字節(jié)由16bit組成,能描述最大的數(shù)字為(2^16) - 1 = 65535。

每個(gè)字段的長(zhǎng)度用1-2字節(jié)表示,那按什么規(guī)則區(qū)分是1個(gè)字節(jié)還是2個(gè)字節(jié)呢?在介紹規(guī)則之前先需要了解變長(zhǎng)字段的最大可能長(zhǎng)度的概念。變長(zhǎng)字段的最大可能長(zhǎng)度的計(jì)算方法為最大字符數(shù) * 字符集最大字節(jié)數(shù),例如上表中列b的最大字節(jié)數(shù)是b,字符集單字符最大字節(jié)數(shù)是3,那么最大可能長(zhǎng)度為30。當(dāng)變長(zhǎng)字段的最大可能長(zhǎng)度大于255時(shí),用一個(gè)字節(jié)記錄其長(zhǎng)度。當(dāng)變長(zhǎng)字段的最大可能長(zhǎng)度大于255時(shí),使用1-2字節(jié)描述字段長(zhǎng)度。具體使用1個(gè)字節(jié)還是2個(gè)字節(jié),使用第一個(gè)字節(jié)的最高bit作為區(qū)分:如果其為0,表示只使用了一個(gè)字節(jié),如果為1表示使用了2個(gè)字節(jié)。當(dāng)只使用一個(gè)字節(jié)時(shí),由于最高bit被用作標(biāo)志,所以其能表示的真實(shí)長(zhǎng)度的范圍是[0, 127],當(dāng)真實(shí)長(zhǎng)度大于127時(shí),需要使用2個(gè)字節(jié)表示。

單個(gè)頁(yè)面大小只有16384字節(jié),而InnoDB規(guī)定單個(gè)頁(yè)面至少需要存放兩條記錄,那么一條記錄最大不得超過(guò)8192字節(jié)。實(shí)際上,算上索引中FIl Header、Page Header、Page Directory、Fil Trailer的空間,那么在頁(yè)面中存儲(chǔ)的記錄的長(zhǎng)度更小。當(dāng)記錄超過(guò)限制大小時(shí),會(huì)出現(xiàn)行溢出的現(xiàn)象,溢出頁(yè)的格式將在第三節(jié)討論。記錄溢出時(shí),對(duì)應(yīng)變長(zhǎng)字段的第一字節(jié)的第二個(gè)bit會(huì)對(duì)其進(jìn)行標(biāo)記,在變長(zhǎng)字段長(zhǎng)度列表處只存儲(chǔ)留在本頁(yè)面中的長(zhǎng)度。至此,變長(zhǎng)字段兩個(gè)字節(jié)中的16個(gè)bit已經(jīng)有兩個(gè)bit用作標(biāo)志(是否用兩字節(jié)存儲(chǔ)長(zhǎng)度,是否有行外數(shù)據(jù)),還能用于描述字段長(zhǎng)度的最大bit數(shù)為14,即最大能表示(2^14) - 1 = 16383字節(jié),描述存儲(chǔ)于當(dāng)前數(shù)據(jù)頁(yè)的記錄長(zhǎng)度仍然綽綽有余。

除上述規(guī)則之外,還需要注意的是變長(zhǎng)字段長(zhǎng)度列表的存儲(chǔ)是按照字段的逆序存放的,與真實(shí)數(shù)據(jù)的存放的順序相反。例如上例中的表t的變長(zhǎng)字段b, c, d在變長(zhǎng)字段列表中的順序是d, c, b。

2.2 NULL值列表

為了節(jié)約空間,值為NULL的字段不會(huì)占用存儲(chǔ)空間,而是通過(guò)NULL標(biāo)記位記錄。只有可能為NULL之的字段才有可能出現(xiàn)在NULL值列表中,如果一個(gè)表的所有列都用NOT NULL修飾,則該表所有記錄都沒(méi)有NULL值列表。

NULL值列表通過(guò)BITMAP來(lái)標(biāo)識(shí)每個(gè)字段是否為空,每個(gè)可能為NULL的字段占一個(gè)bit位標(biāo)識(shí),如果字段為空,則為1,否則為0。與變長(zhǎng)字段列表相似,所有的NULL值也按照字段順序逆序排布。NULL列表占用的存儲(chǔ)空間一定是8 bit的整數(shù)倍,即按字節(jié)為單位存儲(chǔ),如果可以為NULL的字段數(shù)不足8的倍數(shù),在NULL值列表的高位補(bǔ)0。

2.3 記錄頭信息

記錄頭的信息在《InnoDB頁(yè)面結(jié)構(gòu)》中已有部分介紹,此處對(duì)其所有內(nèi)容進(jìn)行介紹。記錄頭包含的信息如下:

內(nèi)容 大小 含義
預(yù)留位 1 暫未使用
預(yù)留位 1 暫未使用
delete_flag 1 是否刪除的標(biāo)識(shí),如果刪除為1,為多版本并發(fā)控制服務(wù)(Multi-Version Concurrency Control ,MVCC)
min_rec_flag 1 B+樹(shù)非葉子結(jié)點(diǎn)中每一層最小的記錄會(huì)添加此標(biāo)識(shí)
n_owned 4 如果有Slot指向此記錄,此字段會(huì)有值并定表此為組長(zhǎng)記錄,記錄此Slot管理的記錄數(shù)
heap_no 13 記錄在頁(yè)面中的物理位置(堆上的位置),每申請(qǐng)一塊記錄空間,都會(huì)為其分配一個(gè) heap_no,從前往后編號(hào),標(biāo)記刪除的記錄不會(huì)減小heap_no
record_type 3 記錄的類(lèi)型,0表示葉子結(jié)點(diǎn)的用戶記錄,1表示非葉子結(jié)點(diǎn)的記錄,2表示Infimum記錄,3表示Supremum記錄
next_record 16 下一條記錄的地址,將頁(yè)面內(nèi)的記錄串聯(lián)起來(lái)

2.4 系統(tǒng)列

InnoDB聚簇索引可能會(huì)存在下述三個(gè)用戶不可見(jiàn)的隱藏系統(tǒng)列:

列名 是否必須 占用空間 描述
DB_ROW_ID 6字節(jié) 行ID,唯一標(biāo)識(shí)一條記錄
DB_TRX_ID 6字節(jié) 事務(wù)ID
DB_ROLL_PTR 7字節(jié) 回滾指針
  • DB_ROW_ID:聚簇索引優(yōu)先使用用戶自定義的主鍵作為Key構(gòu)建B+樹(shù),如果用戶沒(méi)有定義主鍵,則選取一個(gè)Unique鍵作為主鍵,如果表中連Unique鍵都沒(méi)有定義的話,則InnoDB會(huì)為表默認(rèn)添加此隱藏列作為主鍵。所以此列只有在無(wú)主鍵并且無(wú)Unique Key的表中存在。
    此列只有在無(wú)主鍵表中才存在。由于用戶沒(méi)設(shè)置主鍵,InnoDB只能自己添加一個(gè)自增列作為key來(lái)構(gòu)建B+樹(shù)。
  • DB_TRX_ID:表示該行最新修改的事務(wù)ID,為MVCC判斷記錄可見(jiàn)性服務(wù)
  • DB_ROLL_PTR:回滾段指針,指向記錄的上一個(gè)版本,同樣為MVCC判斷記錄可見(jiàn)性服務(wù),當(dāng)前記錄經(jīng)MVCC判斷不可見(jiàn)時(shí),通過(guò)該指針往前回溯記錄的舊版本,找到滿足可見(jiàn)性要求的記錄返回給用戶

二級(jí)索引記錄沒(méi)有DB_TRX_ID和DB_ROLL_PTR,所以其MVCC比較麻煩。二級(jí)索引頁(yè)的Page Header有MAX_TRX_ID字段,表示更新該頁(yè)面的最大事務(wù)ID。如果MAX_TRX_ID小于當(dāng)前事務(wù)開(kāi)啟時(shí)的最小事務(wù)ID,那么萬(wàn)事大吉,此二級(jí)索引頁(yè)面中的非標(biāo)記刪除的二級(jí)索引記錄都是可見(jiàn)的。否則,就需要從二級(jí)索引訪問(wèn)到聚簇索引,通過(guò)聚簇索引再判斷記錄的可見(jiàn)性。

2.5 用戶列

用戶列與列之間沒(méi)有間隔,連續(xù)存放。

3 行溢出處理

3.1 行溢出時(shí)記錄的格式

當(dāng)變長(zhǎng)的字段數(shù)據(jù)過(guò)長(zhǎng),導(dǎo)致索引頁(yè)無(wú)法容納兩條記錄,InnoDB會(huì)將過(guò)長(zhǎng)的字段內(nèi)容存儲(chǔ)到外部存儲(chǔ)頁(yè)(blob page)。不同行格式在此處的處理略有不同。AntelopeRedundantCompact)會(huì)Field內(nèi)容處存儲(chǔ)數(shù)據(jù)內(nèi)容的768字節(jié) + 行外數(shù)據(jù)等地址指針。而Barracuda(DynamicCompressed)只在Field內(nèi)容處記錄行外數(shù)據(jù)等地址指針。

行外數(shù)據(jù)等地址指針占20字節(jié),格式如下:

名稱 大小 內(nèi)容
BTR_EXTERN_SPACE_ID 4 外部存儲(chǔ)頁(yè)的space id
BTR_EXTERN_PAGE_NO 4 外部存儲(chǔ)頁(yè)的頁(yè)碼
BTR_EXTERN_OFFSET 4 外部存儲(chǔ)頁(yè)的頁(yè)內(nèi)偏移。
BTR_EXTERN_LEN 8 數(shù)據(jù)的總大小
  • BTR_EXTERN_OFFSET的取值分兩種情況:當(dāng)外部存儲(chǔ)頁(yè)不是壓縮頁(yè)時(shí),該值為38。其指向外部存儲(chǔ)頁(yè)的Blob Header;當(dāng)外部存儲(chǔ)頁(yè)時(shí)壓縮頁(yè)時(shí),該值為12,指向Fil Header部分的FIL_PAGE_NEXT。
  • BTR_EXTERN_LEN盡管有8個(gè)字節(jié)可以存儲(chǔ)BLOB數(shù)據(jù)的總大小,但實(shí)際上只使用了最后4個(gè)字節(jié)。這意味著在InnoDB中,單個(gè)BLOB字段的最大大小目前為4GB。

3.2 非壓縮外部存儲(chǔ)頁(yè)結(jié)構(gòu)

在非壓縮頁(yè)格式中,外部存儲(chǔ)頁(yè)的管理結(jié)構(gòu)由FIl Header、Blob header、Blob data、Fil Trailer組成,溢出行中地址將指向Blob header。(關(guān)于Fil Header的介紹詳見(jiàn)《InnoDB頁(yè)面結(jié)構(gòu)》)。非壓縮外部存儲(chǔ)頁(yè)的結(jié)構(gòu)如下:

image.png

Blob header的組成如下:

內(nèi)容 大小 含義
BTR_BLOB_HDR_PART_LEN 4 當(dāng)前頁(yè)中存儲(chǔ)的字段的長(zhǎng)度
BTR_BLOB_HDR_NEXT_PAGE_NO 4 如果當(dāng)前頁(yè)面未能存儲(chǔ)所有字段的全部數(shù)據(jù),會(huì)指向下一個(gè)外部存儲(chǔ)頁(yè)面的Page no。

3.3 壓縮外部存儲(chǔ)頁(yè)結(jié)構(gòu)

如果外部存儲(chǔ)頁(yè)為壓縮格式,其直接由Fil Header、壓縮數(shù)據(jù)、Fil Trailer組成。溢出行中地址將指向Fil Header中的FIL_PAGE_NEXT(頁(yè)內(nèi)偏移為12)。壓縮外部存儲(chǔ)頁(yè)的結(jié)構(gòu)如下圖所示:


image.png

4 其他行格式對(duì)比

4.2 Redundant

如前所述,Redundant是非緊湊型行格式,比較占用磁盤(pán)空間。Redundant行格式與Dynamic格式的不同之處在于并沒(méi)有區(qū)分定長(zhǎng)和變長(zhǎng)字段,而是將所有列占用的存儲(chǔ)空間都逆序存儲(chǔ)在字段長(zhǎng)度偏移列表中。并且 Redundant格式并不存在NULL值列表,使用字段長(zhǎng)度值的第1位來(lái)判斷字段是否為空,如果第1位為1,則為空。因?yàn)榈?位用來(lái)記錄字段是否為NULL,所以一個(gè)字節(jié)所能表示的最大長(zhǎng)度為127。

Redundant格式的記錄頭占用了6個(gè)字節(jié),分為了9部分,相較于Dynamic格式多了n_field和1byte_offs_flag字段,少了record_type字段,格式如下所示:

名稱 大小 內(nèi)容
預(yù)留位 1 暫未使用
預(yù)留位 1 暫未使用
delete_flag 1 是否刪除的標(biāo)識(shí),如果刪除為1
min_rec_flag 1 B+樹(shù)非葉子結(jié)點(diǎn)中每一層最小的記錄會(huì)添加此標(biāo)識(shí)
n_owned 4 如果有slot指向此記錄,此字段會(huì)有值,記錄此slot管理的記錄數(shù)
heap_no 13 記錄在頁(yè)面中的物理位置(堆上的位置),每申請(qǐng)一塊記錄空間,都會(huì)為其分配一個(gè) heap_no,從前往后編號(hào)
n_field 10 記錄中列的數(shù)量
1byte_offs_flag 1 標(biāo)識(shí)字段長(zhǎng)度偏移列表中字段的長(zhǎng)度用1個(gè)字節(jié)還是2個(gè)字節(jié)來(lái)表示,如果所有字段長(zhǎng)度小于127,則用一個(gè)字節(jié)表示,如果大于127,則用兩個(gè)字段表示
next_record 16 下一條記錄的地址,將頁(yè)面內(nèi)的記錄串聯(lián)起來(lái)

4.2 Compact

Compact是一種緊湊類(lèi)型的存儲(chǔ)格式,與Dynamic類(lèi)型的存儲(chǔ)格式基本一致。如第三節(jié)所述,作為Antelope,其溢出行的處理方式是在索引頁(yè)存儲(chǔ)變長(zhǎng)字段的前768字節(jié)的數(shù)據(jù)+外部存儲(chǔ)頁(yè)指針,因此其變長(zhǎng)字段長(zhǎng)度為768+20。與Redundant格式相比,Compact行格式減少了約20%的行存儲(chǔ)空間。

4.3 Compressed

Compressed類(lèi)型與Dynamic類(lèi)型擁有相同的存儲(chǔ)特性和功能,不同之處在于使用壓縮算法對(duì)頁(yè)面進(jìn)行壓縮,包括溢出頁(yè)。優(yōu)點(diǎn)在于可以節(jié)約存儲(chǔ)空間,但是在查找數(shù)據(jù)時(shí)需要先解壓才行,會(huì)消耗更多的CPU資源。

Compressed行格式必須在建表時(shí)指定,而且需要同時(shí)指定KEY_BLOCK_SIZE。KEY_BLOCK_SIZE會(huì)控制壓縮后頁(yè)面的大小,指定的大小必須小于當(dāng)前默認(rèn)數(shù)據(jù)頁(yè)的大小。如果沒(méi)有指定KEY_BLOCK_SIZE,則會(huì)自動(dòng)設(shè)置為默認(rèn)數(shù)據(jù)頁(yè)大小的一半。如果要使通用表空間包含壓縮表,必須指定FILE_BLOCK_SIZE選項(xiàng),如果小于當(dāng)前默認(rèn)數(shù)據(jù)頁(yè)的大小,會(huì)自動(dòng)設(shè)置為Compressed格式。其中FILE_BLOCK_SIZE的單位為Byte,KEY_BLOCK_SIZE的單位為KB。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容