1. 程式人生 > >【大白話系列】MySQL 學習總結 之 COMPACT 行格式的設計原理

【大白話系列】MySQL 學習總結 之 COMPACT 行格式的設計原理

如果大家對我的 【大白話系列】MySQL 學習總結系列 感興趣的話,可以點選關注一波。

一、回顧

MySQL 學習總結系列至此已經第七節了。

從大方向:我們已經學習了 MySQL 的架構設計、InnoDB 的架構設計。

從較為深入的:我們已經學習了 rodo log 和 binlog 配合的兩階段提交協議,瞭解 緩衝池的設計原理和支援高併發、動態調整的管理機制。

下面,我們將介紹資料行格式:資料是以什麼格式儲存在資料頁中的。

二、行儲存格式

InnoDB 儲存引擎支援有四種行儲存格式:COMPACT、Redundant、Dynamic 和 COMPRESSED。

下面我們將重點介紹 COMPACT 行格式:

COMPACT 行儲存格式大概類似這樣:

變長欄位的長度列表,null值列表,資料頭,column01的值,column02的值,column0n的值......

ps:為了讓磁碟空間得到最大的利用率,每個資料行都是緊緊地挨在一起的。

下面我們將詳細介紹 COMPACT 行格式的各個知識點,當你學習完之後,你就曉得即使每行資料緊緊地挨在一起,MySQL 也能精準地將每行資料找出來~

三、變長欄位如何儲存?

1、變長欄位的儲存問題

我們都知道,varchar 型別是變長的,例如 varchar(50),那麼這個欄位值的長度範圍:0 ~ 50 個字元。但是,不是每個欄位值都剛好50個字元,肯定會有的長有的短。

那麼,資料儲存時,會按照欄位定義時的最大長度來儲存值嗎?

必須不會的,如果都按照最大長度儲存,當出現值不滿 50個字元長度時,會浪費磁碟空間和記憶體空間。

為什麼也浪費記憶體空間,資料不是存放在磁碟麼?大家不會忘了緩衝池的作用了吧?哈哈,要記得緩衝池和磁碟資料交換的單位就是資料頁~而資料行是存放在資料頁中的~

2、變長欄位長度列表

InnoDB 中,利用 變長欄位長度列表 來解決上面的問題:

  1. 變長欄位長度列表記錄每一個變長欄位值的長度,儲存的長度是十六進位制的。
  2. 如果有多個變長欄位,那麼變長欄位長度列表是按逆序儲存的。

下面用一個例子來描述一下變長欄位長度列表的使用原理:

-- 表結構
create table test(
    c1 varchar(10) comment '欄位1-變長',
    c2 varchar(5) comment '欄位2-變長',
    c3 varchar(20) comment '欄位3-變長',
    c4 char(1) comment '欄位4-定長',
    c5 char(1) comment '欄位5-定長'
) ENGINE=InnoDB;
-- 一行資料
insert into test values('hello','ni','hao','a','a');

我們來算一下他們的長度(十六進位制):

  1. hello 的長度為5,十六進位制為 0x05
  2. ni 的長度為2,十六進位制為 0x02
  3. hao 的長度為3,十六進位制為 0x03

那麼,實際的儲存格式是這樣的:

0x03 0x02 0x05 null值列表 資料頭 hello hi hao a a

四、NULL 值欄位如何儲存?

1、可為 NULL 欄位的儲存問題

定義為 default NULL 的欄位,值可空可不空。那如果欄位值為 NULL,資料行裡是怎樣儲存的呢?是直接儲存“NULL”欄位嗎?

我們分析一下:

  1. 如果是,那將會浪費磁碟空間,本來值就是 NULL 的,你現在給我搞了個四個字元大小的字串。
  2. 如果不是,那怎麼識別這個欄位是否是 NULL 呢?

2、NULL值列表

InnoDB 中,利用 NULL值列表 來解決上面的問題:

  1. NULL 值列表記錄可為 NULL 的欄位的情況。
  2. 用二進位制bit位來標識欄位值是否為 NULL。1為 NULL,0 不為 NULL。
  3. 如果有多個可為 NULL 的欄位,那麼 NULL 值列表也是按照逆序儲存的。
  4. 而且 NULL 值列表的位數必須是 8bit 的N倍。例如:列表僅僅只有4個bit,則往高位補0,補到 8個bit。

下面用一個例子來描述一下 NULL 值列表 的使用原理:

-- 表結構
create table test(
    c1 varchar(10) not null comment '欄位1-變長',
    c2 varchar(5) comment '欄位2-變長',
    c3 char(1) comment '欄位3-變長',
    c4 varchar(30) comment '欄位4-定長',
    c5 varchar(50) comment '欄位5-定長'
) ENGINE=InnoDB;
-- 一行資料
insert into test values('howinfun',null,'m',null,'foshan');

算一下變長欄位的長度:

  1. howinfun 的長度為8,十六進位制為 0x08
  2. foshan 的長度為6,十六進位制為 0x06

統計一下值為 NULL 的欄位:

  1. c2 欄位為 NULL
  2. c4 欄位為 NULL

那麼,實際的儲存格式是這樣的:

0x06 0x08 00000101 資料頭 howinfun m foshan

3、採用 NULL值列表 和 直接儲存“NULL”字串相比,有多大的儲存差距?

到此,我們就可以算一下這兩種方案的儲存差距有多大了。

  1. 一個位元組 8個bit,NULL 值列表用二進位制 bit 位來標識欄位值是否為 NULL;那麼就是說,標識8個欄位才佔用一個位元組。
  2. 而如果用字串的方式來儲存,而一個"NULL"字串足足用了四個位元組(英文一個字元等於一個位元組,中文一個字元等於兩個位元組),那麼同樣的8個欄位就需要36個位元組了。

這差距是非常明顯的!

五、資料頭

COMPACT 行格式中,除了 變長欄位長度列表 和 NULL 值列表,就到資料頭了。

資料頭的大小為 40 個bit位。

下面介紹 40個 bit 分別都有什麼資訊。

名稱 大小 (bit) 描述
預留位1 1 沒有使用
預留位2 1 沒有使用
delete_mask 1 標記該記錄是否被刪除
min_rec_mask 1 B+樹裡每一層的非葉子節點裡的最小值都有這個標記
n_owned 4 表示當前記錄擁有的記錄數
heap_no 13 表示當前記錄在記錄堆的位置資訊
record_type 3 標識當前記錄的型別:0代表的是普通型別,1代表的是B+樹非葉子節點,2代表的是最小值資料,3代表的是最大值資料。
next_record 16 表示下一條記錄的相對位置

那麼,我們看一下,加上資料頭的實際儲存:

0x06 0x08 00000101 0000000000000000000010000000000000011001 howinfun m foshan

六、一行資料在磁碟是如何儲存的

1、字符集編碼

上面,我們已經介紹了 COMPACT 行格式了,那麼一行資料真正是如何儲存的?

我們都知道,在建庫和建表時,都可以指定字符集編碼。所以,資料都會經過資料庫指定的字符集編碼後,再進行儲存的。

下面用一個例子來描述一下使用原理:

-- 表結構
create table test(
    c1 varchar(10) not null comment '欄位1',
    c2 varchar(5) comment '欄位2',
    c3 char(1) comment '欄位3',
    c4 varchar(30) comment '欄位4',
    c5 varchar(50) comment '欄位5'
)
-- 一行資料
insert into test values('howinfun',null,'m',null,'foshan');

假設編碼後:

  1. howinfun 編碼後:61616161
  2. m 編碼後:62
  3. foshan編碼後:636363

那麼,實際的儲存格式是這樣的:

0x06 0x08 00000101 0000000000000000000010000000000000011001 61616161 62 636363

2、隱藏欄位

除了變長欄位長度列表、NULL值列表、40個bit位的資料頭和真實資料,其實還包含了一些隱藏欄位:

  1. DB_ROW_ID 欄位:如果我們沒有指定主鍵和unique key唯一索引的時候,他就內部自動加一個ROW_ID作為主鍵。
  2. DB_TRX_ID 欄位:事務 ID,標識這是哪個事務更新的資料
  3. DB_ROLL_PTR 欄位:回滾指標,用來進行事務回滾的

加上隱藏欄位後,上面的例子的實際儲存可能就是:

0x06 0x08 00000101 0000000000000000000010000000000000011001 00000000094C(DB_ROW_ID)00000000032D(DB_TRX_ID) EA000010078E(DB_ROL_PTR) 616161 636320 6262626262 

ps:括號裡只是做說明用的,事實是不存在的。

3、行溢位問題

資料頁的預設大小是 16kb,但是某些欄位的值可以遠遠大於 16kb。

例如變長欄位型別 varchar(N):N 最大可為 65532(65kb),這就遠遠大於 16kb。

當然了,還有 text 和 blog 欄位,這些都是大欄位,都可以超過 16kb。

如果一行資料的大小超過了 16kb,就會出現行溢位的現象。

怎麼解決?

當一行資料超了 16kb,會在超了大小的那個欄位中,可能僅僅包含他的一部分資料,然後同時包含一個20個位元組的指標,指向儲存了這行資料超了的部分的其他資料頁