1. 程式人生 > >Mysql系列(四)—— InnoDB的行格式

Mysql系列(四)—— InnoDB的行格式

寫在前面

本文涉及知識主要學習自作者小孩子的掘金專欄:

一、InnoDb中的頁

我們知道,要處理資料,必須先把資料放到記憶體中來,那麼Mysql讀寫記錄時,是怎麼讀寫的勒?Mysql是將資料劃分為若干個頁,以頁作為磁碟和記憶體之間互動的基本單位,InnoDB中頁的大小一般為 16 KB。也就是在一般情況下,一次最少從磁碟中讀取16KB的內容到記憶體中,一次最少把記憶體中的16KB內容重新整理到磁碟中。

二、InnoDB有哪些行格式

所謂行格式就是表的一條記錄在磁盤裡儲存的二進位制格式。迄今為止,InnoDB有四種行格式,分別是Compact、Redundant、Dynamic和Compressed行格式。下面分別介紹下這幾種行格式。

(一)Compact

示意圖:

一條記錄儲存分為記錄的額外資訊和記錄的真實資料兩部分:

1.記錄的額外資訊

記錄的額外資訊又包括變長欄位長度列表、NULL值列表、記錄頭資訊:

(1)變長欄位長度列表

所謂變長欄位,指的是如VARCHAR(M)、VARBINARY(M)、各種TEXT型別,各種BLOB型別的欄位,變長欄位列表主要是儲存的這些欄位的真實資料佔用的位元組長度,該列表的順序是按表字段逆序。在長度列表中每個欄位用1-2個位元組來其位元組長度,具體是1還是2個位元組是通過該記錄該欄位佔用的最大位元組長度和真實資料長度來計算得到的。具體規則是:先看欄位最大位元組長度,小於255直接用1個位元組表示,那如果大於255的勒?比如utf8編碼格式下的varchar(100),最大位元組長度是3*100=300,超過了一個位元組能表示的最大的數。這時應該看真實資料字元佔用位元組數,如果真實資料字元佔用資料位元組數小於127,用1個位元組,大於用2個位元組。為什麼是用127做劃分勒,因為一個位元組有8位,首位被用來標識需不需要一起讀取下個位元組作為欄位的位元組長度(即這個欄位用的是1個位元組表示長度還是2個位元組)。0需要,1表示不需要。如果碰到該記錄資料位元組太長,產生行溢位時(後面會細講),這種情況的話,變長欄位長度列表中表示該欄位的長度還是2個位元組,只表示該記錄在本頁的資料長度。因為2個位元組所能表示的位元組長度有2的15次方,遠遠大於InnoDb讀寫一頁(16KB)的長度了,所以就算該記錄只有一個欄位,本頁資料全存該欄位的資料,那2個位元組來表示本頁所佔長度也是完全放得下的。

注:對於 CHAR(M) 型別的列來說,當列採用的是定長字符集時,該列佔用的位元組數不會被加到變長欄位長度列表,而如果採用變長字符集時,該列佔用的位元組數也會被加到變長欄位長度列表。另外定長字符集下的CHAR型別欄位,如果涉及更新或刪除的話,不會產生硬碟碎片,效率比varchar高。

(2)NULL值列表

null值列表只有在該記錄所在表的元資料規定有欄位可以存在null值才會有null值列表,null值列表是基於位向量來維護欄位是否為null的,即用二進位制位的0和1表示欄位是否為null,該列表也是逆序的。另外InnoDB還規定NULL值列表必須用整數個位元組的位表示,如果使用的二進位制位個數不是整數個位元組,則在位元組的高位補0。

(3)記錄頭資訊

記錄頭資訊由固定5個位元組組成包括:

上圖中,有些概念可能不清楚,後面如果再開文章的話再學習。

2.記錄的真實資料

記錄的真實資料除了使用者自己定義的列的資料以外,InnoDB還會為每個記錄預設的新增一些列(也稱為隱藏列),具體的列如下:

其中row_id不一定是必須的,只有在表中不存在主鍵的時候InnoDB才會自動新增這列。

(二)Redundant

示意圖:

這個行格式名稱是也就是Redundant,表示它是已經是過時了的了,現在一般不用,但這裡還是介紹一下,對比與Compact的區別。

1.欄位長度偏移列表

與變長欄位長度列表有兩處不同:

  • 沒有了變長兩個字,意味著Redundant行格式會把該條記錄中所有列(包括隱藏列)的長度資訊都按照逆序儲存到欄位長度偏移列表。
  • 多了個偏移兩個字,這意味著計算列值長度的方式不像Compact行格式那麼直觀,它是採用兩個相鄰數值的差值來計算各個列值的長度。比如每個欄位長度按逆序列表的10進製表示是6、12、9,那麼偏移之後就是6、18(18-6=12)、27(27-18=9)。

從上面看出,欄位長度偏移列表實質上是儲存每個列中的值佔用的空間在記錄的真實資料處結束的位置,這種表示方法相對來說更簡單直觀。

注:對於到底用1個位元組或2個位元組,規則類似Compact,但判斷的是該記錄所有欄位真實資料長度,如果真實資料小於127位元組,則每個列對應的偏移量佔用1個位元組,大於127,用兩個位元組來劃分,當然真實資料可能超過了2個位元組所能表示的最大位元組數32767,這時依舊是兩個位元組,原因同Compact,2個位元組足夠表示該頁的最大偏移(因為1頁就16K,也就是16384個位元組),剩下的為溢位列資料,交由其他頁存放,本頁只存其他頁的指向地址。

2.記錄頭資訊

與Compact的記錄頭資訊相比:

  • Redundant行格式多了n_field和1byte_offs_flag這兩個屬性。

Redundant跟Compact不一樣的是,把是一個或兩個位元組表示長度放在了頭資訊裡面,具體規則類似Compact,但判斷的是該記錄所有欄位真實資料長度,如果真實資料小於127位元組,則每個列對應的偏移量佔用1個位元組,大於127(這裡可能會疑問為什麼是127而不是255(1個位元組表示的最大的數),因為與Compact格式不同,Redundant對null值資訊沒有集中儲存,而是將欄位長度偏移列表首個位元組利用起來,標識了該欄位為不為null),用兩個位元組來劃分,當然真實資料可能超過了2個位元組所能表示的最大位元組數32767,這時依舊是兩個位元組,原因同Compact,2個位元組足夠表示該頁的最大偏移(因為1頁就16K,也就是16384個位元組),剩下的為溢位列資料,交由其他頁存放,本頁只存其他頁的指向地址。

  • Redundant行格式沒有record_type這個屬性。

(三)Dynamic

與Compact的區別是:

不會在記錄的真實資料處儲存欄位真實資料的前768個位元組,而是把所有的位元組都儲存到其他頁面中,只在記錄的真實資料處儲存其他頁面的地址。

(四)Compressed

與Dynamic不同的一點是:

Compressed行格式會採用壓縮演算法對頁面進行壓縮,以節省空間。

三、如何檢視行格式

在我使用的版本Mysql5.7.26版本中,行格式預設為Dynamic。如何進行檢視某個表的行格式命令是:

show table STATUS like '表名'

三、指定行格式的語法

建立或修改表的語句:

  • CREATE TABLE 表名 (列的資訊) ROW_FORMAT=行格式名稱
  • ALTER TABLE 表名 ROW_FORMAT=行格式名稱

四、針對行溢位資料的處理

(一)什麼是行溢位

  • DDL定義時報錯欄位溢位

MySQL對一條記錄佔用的最大儲存空間是有限制的,除了BLOB或者TEXT型別的列之外,其他所有的列(不包括隱藏列和記錄頭資訊)佔用的位元組長度加起來不能超過65535個位元組。如果超過會報ERROR,比如建立一個只有一個欄位編碼格式為ascii(1個位元組為1個字元)的表:

CREATE TABLE varchar_size_demo(
    ->     c VARCHAR(65535)
    -> ) CHARSET=ascii ROW_FORMAT=Compact;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

注:這裡的65535包含除了列本身的資料之外,還包括一些其他的資料(storage overhead)如NULL值標識(可以為null的欄位需要這個標識)、真實資料佔用位元組的長度。這樣可以計算一下,如果只有一個欄位的表,可以為null,那麼該欄位真實資料可用位元組就是65535-2(真實資料佔用位元組的長度)-1(null值標識)=65532,以上語句可以改為:

CREATE TABLE varchar_size_demo(
     c VARCHAR(65532)
) CHARSET=ascii ROW_FORMAT=Redundant
> OK
> 時間: 0.124s

這樣就恰恰夠裝。

  • 執行時的行溢位(不報錯)

在Compact和Reduntant行格式中,對於佔用儲存空間非常大的列,在記錄的真實資料處只會儲存該列的一部分資料,把剩餘的資料分散儲存在幾個其他的頁中,然後記錄的真實資料處用20個位元組儲存指向這些頁的地址(當然這20個位元組中還包括這些分散在其他頁面中的資料的佔用的位元組數),從而可以找到剩餘資料所在的頁。如圖:

那麼這裡怎麼計算,產生行溢位的資料長度的臨界點勒?這裡與幾個限制有關:

  • MySQL中規定一個頁中至少存放兩行記錄
  • 一頁只有16K

即只要保證2條資料,加起來資料大小不超過16K減去頁中其他不用於儲存記錄的大小(固定132個位元組),就不會產生行溢位,但是一條資料不止有儲存真實資料還有其他。以Compact為例,假設只有1個欄位,且可以為Null,則每個記錄需要的額外資訊是27位元組,包括:

  • 2個位元組用於儲存真實資料的長度
  • 1個位元組用於儲存列是否是NULL值(不超過255個欄位可以為null,所以直接用1個位元組)
  • 5個位元組大小的頭資訊
  • 6個位元組的row_id列
  • 6個位元組的transaction_id列
  • 7個位元組的roll_pointer列

假設一個列中儲存的資料位元組數為n,只要滿足:

132 + 2×(27 + n) < 16384

則該記錄不會造成行溢位。當然如果表中有多個欄位,上面公式中的27可能會增加(因為“真實資料的長度”和“列是否是NULL值”佔用位元組