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

1、InnoDb中的頁

咱們知道,要處理數據,必須先把數據放到內存中來,那麼Mysql讀寫記錄時,是怎麼讀寫的勒?Mysql是將數據劃分爲若干個頁,以頁做爲磁盤和內存之間交互的基本單位,InnoDB中頁的大小通常爲 16 KB。也就是在通常狀況下,一次最少從磁盤中讀取16KB的內容到內存中,一次最少把內存中的16KB內容刷新到磁盤中。算法

2、InnoDB有哪些行格式

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

(一)Compact

示意圖:學習

一條記錄存儲分爲記錄的額外信息和記錄的真實數據兩部分:編碼

1.記錄的額外信息

記錄的額外信息又包括變長字段長度列表、NULL值列表、記錄頭信息:code

(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個字節來表示本頁所佔長度也是徹底放得下的。blog

注:對於 CHAR(M) 類型的列來講,當列採用的是定長字符集時,該列佔用的字節數不會被加到變長字段長度列表,而若是採用變長字符集時,該列佔用的字節數也會被加到變長字段長度列表。另外定長字符集下的CHAR類型字段,若是涉及更新或刪除的話,不會產生硬盤碎片,效率比varchar高。內存

(2)NULL值列表

null值列表只有在該記錄所在表的元數據規定有字段能夠存在null值纔會有null值列表,null值列表是基於位向量來維護字段是否爲null的,即用二進制位的0和1表示字段是否爲null,該列表也是逆序的。另外InnoDB還規定NULL值列表必須用整數個字節的位表示,若是使用的二進制位個數不是整數個字節,則在字節的高位補0。ci

(3)記錄頭信息

記錄頭信息由固定5個字節組成包括:io

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

2.記錄的真實數據

記錄的真實數據除了用戶本身定義的列的數據之外,InnoDB還會爲每一個記錄默認的添加一些列(也稱爲隱藏列),具體的列以下:

其中row_id不必定是必須的,只有在表中不存在主鍵的時候InnoDB纔會自動添加這列。

(二)Redundant

示意圖:

這個行格式名稱是也就是Redundant,表示它是已是過期了的了,如今通常不用,但這裏仍是介紹一下,對比與Compact的區別。

1.字段長度偏移列表

與變長字段長度列表有兩處不一樣:

  • 沒有了變長兩個字,意味着Redundant行格式會把該條記錄中全部列(包括隱藏列)的長度信息都按照逆序存儲到字段長度偏移列表。
  • 多了個偏移兩個字,這意味着計算列值長度的方式不像Compact行格式那麼直觀,它是採用兩個相鄰數值的差值來計算各個列值的長度。好比每一個字段長度按逆序列表的10進製表示是六、十二、9,那麼偏移以後就是六、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行格式會採用壓縮算法對頁面進行壓縮,以節省空間。

3、如何查看行格式

在我使用的版本Mysql5.7.26版本中,行格式默認爲Dynamic。如何進行查看某個表的行格式命令是:

show table STATUS like '表名'

3、指定行格式的語法

建立或修改表的語句:

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

4、針對行溢出數據的處理

(一)什麼是行溢出

  • 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值」佔用字節可能會增長)。

相關文章
相關標籤/搜索