InnoDB行格式

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

1 行格式總覽

InnoDB規(guī)劃了26種行格式,分別對應(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í),可以通過 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ǔ)的記錄是非緊湊類型的,比較占用磁盤空間。同樣的頁面中存儲(chǔ)的記錄行更少,索引的效率較低。目前已很少使用。Compact、Dynamic、Compressed三種行格式結(jié)構(gòu)比較相似。由于MySQL 5.7和8.0默認(rèn)的行格式為Dynamic,下面將展開介紹Dynamic行格式。

2 Dynamic格式

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

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

2.1 變長字段長度列表

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

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

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

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

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

2.2 NULL值列表

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

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

2.3 記錄頭信息

記錄頭的信息在《InnoDB頁面結(jié)構(gòu)》中已有部分介紹,此處對其所有內(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+樹非葉子結(jié)點(diǎn)中每一層最小的記錄會(huì)添加此標(biāo)識(shí)
n_owned 4 如果有Slot指向此記錄,此字段會(huì)有值并定表此為組長記錄,記錄此Slot管理的記錄數(shù)
heap_no 13 記錄在頁面中的物理位置(堆上的位置),每申請一塊記錄空間,都會(huì)為其分配一個(gè) heap_no,從前往后編號(hào),標(biāo)記刪除的記錄不會(huì)減小heap_no
record_type 3 記錄的類型,0表示葉子結(jié)點(diǎn)的用戶記錄,1表示非葉子結(jié)點(diǎn)的記錄,2表示Infimum記錄,3表示Supremum記錄
next_record 16 下一條記錄的地址,將頁面內(nèi)的記錄串聯(lián)起來

2.4 系統(tǒng)列

InnoDB聚簇索引可能會(huì)存在下述三個(gè)用戶不可見的隱藏系統(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+樹,如果用戶沒有定義主鍵,則選取一個(gè)Unique鍵作為主鍵,如果表中連Unique鍵都沒有定義的話,則InnoDB會(huì)為表默認(rèn)添加此隱藏列作為主鍵。所以此列只有在無主鍵并且無Unique Key的表中存在。
    此列只有在無主鍵表中才存在。由于用戶沒設(shè)置主鍵,InnoDB只能自己添加一個(gè)自增列作為key來構(gòu)建B+樹。
  • DB_TRX_ID:表示該行最新修改的事務(wù)ID,為MVCC判斷記錄可見性服務(wù)
  • DB_ROLL_PTR:回滾段指針,指向記錄的上一個(gè)版本,同樣為MVCC判斷記錄可見性服務(wù),當(dāng)前記錄經(jīng)MVCC判斷不可見時(shí),通過該指針往前回溯記錄的舊版本,找到滿足可見性要求的記錄返回給用戶

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

2.5 用戶列

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

3 行溢出處理

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

當(dāng)變長的字段數(shù)據(jù)過長,導(dǎo)致索引頁無法容納兩條記錄,InnoDB會(huì)將過長的字段內(nèi)容存儲(chǔ)到外部存儲(chǔ)頁(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ǔ)頁的space id
BTR_EXTERN_PAGE_NO 4 外部存儲(chǔ)頁的頁碼
BTR_EXTERN_OFFSET 4 外部存儲(chǔ)頁的頁內(nèi)偏移。
BTR_EXTERN_LEN 8 數(shù)據(jù)的總大小
  • BTR_EXTERN_OFFSET的取值分兩種情況:當(dāng)外部存儲(chǔ)頁不是壓縮頁時(shí),該值為38。其指向外部存儲(chǔ)頁的Blob Header;當(dāng)外部存儲(chǔ)頁時(shí)壓縮頁時(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ǔ)頁結(jié)構(gòu)

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

image.png

Blob header的組成如下:

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

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

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


image.png

4 其他行格式對比

4.2 Redundant

如前所述,Redundant是非緊湊型行格式,比較占用磁盤空間。Redundant行格式與Dynamic格式的不同之處在于并沒有區(qū)分定長和變長字段,而是將所有列占用的存儲(chǔ)空間都逆序存儲(chǔ)在字段長度偏移列表中。并且 Redundant格式并不存在NULL值列表,使用字段長度值的第1位來判斷字段是否為空,如果第1位為1,則為空。因?yàn)榈?位用來記錄字段是否為NULL,所以一個(gè)字節(jié)所能表示的最大長度為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+樹非葉子結(jié)點(diǎn)中每一層最小的記錄會(huì)添加此標(biāo)識(shí)
n_owned 4 如果有slot指向此記錄,此字段會(huì)有值,記錄此slot管理的記錄數(shù)
heap_no 13 記錄在頁面中的物理位置(堆上的位置),每申請一塊記錄空間,都會(huì)為其分配一個(gè) heap_no,從前往后編號(hào)
n_field 10 記錄中列的數(shù)量
1byte_offs_flag 1 標(biāo)識(shí)字段長度偏移列表中字段的長度用1個(gè)字節(jié)還是2個(gè)字節(jié)來表示,如果所有字段長度小于127,則用一個(gè)字節(jié)表示,如果大于127,則用兩個(gè)字段表示
next_record 16 下一條記錄的地址,將頁面內(nèi)的記錄串聯(lián)起來

4.2 Compact

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

4.3 Compressed

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

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

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

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

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