MySQL原理 - InnoDB引擎 - 行記錄存儲 - Redundant行格式

本文基於 MySQL 8html

上一篇:MySQL原理 - InnoDB引擎 - 行記錄存儲 - Compact格式 中,咱們介紹了什麼是 InnoDB 行記錄存儲以及 Compact 行格式,在這一篇中,咱們繼續介紹其餘三種行格式。mysql

Redundant 行格式

這個是最古老的,最簡單粗暴的行格式了,如今基本上已經不用了,由於佔用空間最多,從而致使內存碎片化最嚴重,是最低效的行格式了(針對如今varchar字段使用的更多,而對於 varchar 字段改變長度的更新大部分狀況下就是將原有行的數據標記爲已刪除,而後在其餘空間足夠的地方新建記錄,Redundant 顧名思義,佔用空間更多,因此碎片化,空間浪費會更嚴重)。sql

MySQL官網的 Internal Mannual 給出的行格式示例,其實就是 Redundant 格式的: InnoDB Record High-Altitude Picturebash

建立一個和上一篇中的示例同樣的表,插入相同的數據:ide

CREATE TABLE `record_test_2` (
  `id` bigint(20) DEFAULT NULL,
  `score` double DEFAULT NULL,
  `name` char(4) DEFAULT NULL,
  `content` varchar(8) DEFAULT NULL,
  `extra` varchar(16) DEFAULT NULL,
  `large_content` varchar(1024) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=REDUNDANT
複製代碼
INSERT INTO `record_test_2`(`id`, `score`, `name`, `content`, `extra`, `large_content`) VALUES (1, 78.5, 'hash', 'wodetian', 'nidetiantadetian', 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz');
INSERT INTO `record_test_2`(`id`, `score`, `name`, `content`, `extra`, `large_content`) VALUES (65536, 17983.9812, 'zhx', 'shin', 'nosuke', 'lex');
INSERT INTO `record_test_2`(`id`, `score`, `name`, `content`, `extra`, `large_content`) VALUES (NULL, -669.996, 'aa', NULL, NULL, NULL);
INSERT INTO `record_test_2`(`id`, `score`, `name`, `content`, `extra`, `large_content`) VALUES (2048, NULL, NULL, 'c', 'jun', '');
INSERT INTO `record_test_2`(`id`, `score`, `name`, `content`, `extra`, `large_content`) VALUES (-1, 26.75, 'xxxx', 'aaaa', 'bbbb', 'cccc');

複製代碼

咱們來直接看底層存儲的數據是什麼樣子的:post

image

全部字段長度列表:00 c1 00 3f 00 2f 00 27 00 23 00 1b 00 13 00 0c 00 06 
記錄頭信息:00 00 10 12 01 65 
隱藏列DB_ROW_ID:00 00 00 00 09 00 
隱藏列DB_TRX_ID:00 00 00 03 cb 08 
隱藏列DB_ROLL_PTR:a8 00 00 01 1c 01 10 
列數據id(1):80 00 00 00 00 00 00 01 
列數據score(78.5):00 00 00 00 00 a0 53 40 
列數據name(hash):68 61 73 68 
列數據content(wodetian):77 6f 64 65 74 69 61 6e 
列數據extra(nidetiantadetian):6e 69 64 65 74 69 61 6e 74 61 64 65 74 69 61 6e 
列數據large_content(abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz):61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 

全部字段長度列表:34 31 2b 27 23 1b 13 0c 06 
記錄頭信息:00 00 18 13 01 a8 
隱藏列DB_ROW_ID:00 00 00 00 09 01 
隱藏列DB_TRX_ID:00 00 00 03 cb 09 
隱藏列DB_ROLL_PTR:a9 00 00 02 01 01 10 
列數據id(65536):80 00 00 00 00 01 00 00 
列數據score(17983.9812):b5 15 fb cb fe 8f d1 40 
列數據name(zhx):7a 68 78 20 
列數據content(shin):73 68 69 6e 
列數據extra(nosuke):6e 6f 73 75 6b 65 
列數據large_content(lex):6c 65 78 

全部字段長度列表:a7 a7 a7 27 23 9b 13 0c 06 
記錄頭信息:00 00 00 13 01 de 
隱藏列DB_ROW_ID:00 00 00 00 09 02 
隱藏列DB_TRX_ID:00 00 00 03 cb 0e 
隱藏列DB_ROLL_PTR:ac 00 00 01 00 01 10 
列數據id(null):00 00 00 00 00 00 00 00 
列數據score(-669.996):87 16 d9 ce f7 ef 84 c0 
列數據name(aa):61 61 20 20 

全部字段長度列表:ab 2b 28 a7 a3 1b 13 0c 06 
記錄頭信息:00 00 28 13 02 18 
隱藏列DB_ROW_ID:00 00 00 00 09 03 
隱藏列DB_TRX_ID:00 00 00 03 cb 0f 
隱藏列DB_ROLL_PTR:ad 00 00 01 21 01 10 
列數據id(2048):80 00 00 00 00 00 08 00 
列數據score(null):00 00 00 00 00 00 00 00 
列數據name(null):00 00 00 00 
列數據content(c):63 
列數據extra(jun):6a 75 6e 

全部字段長度列表:33 2f 2b 27 23 1b 13 0c 06 
記錄頭信息:00 00 30 13 00 74 
隱藏列DB_ROW_ID:00 00 00 00 09 04 
隱藏列DB_TRX_ID:00 00 00 03 cb 10 
隱藏列DB_ROLL_PTR:ae 00 00 01 22 01 10
列數據id(-1):7f ff ff ff ff ff ff ff 
列數據score(26.75):00 00 00 00 00 c0 3a 40 
列數據name(xxxx):78 78 78 78 
列數據content(aaaa):61 61 61 61 
列數據extra(bbbb):62 62 62 62 
列數據large_content(cccc):63 63 63 63  
複製代碼

Redundant - 全部字段長度列表

不一樣於 Compact 行格式,Redundant 的開頭是全部字段長度列表,而不是變長字段列表 + NULL 值列表。這個字段長度列表的格式是:ui

  • 記錄全部字段的長度偏移,包括隱藏列。偏移就是,第一個字段長度爲 a,第二個字段長度爲 b,那麼列表中第一個字段就是 a,第二個字段就是 a + b。
  • 全部字段倒序排列

對於長度存儲,是一字節仍是兩字節,以及存儲的內容,Redundant 的規則比較特殊:編碼

  • 根據整行記錄的長度決定,到底每一個字段用一個字節仍是兩個字節,每一個字段用一個字節仍是兩個字節,在記錄頭信息裏面有標記
    • 若是整行長度小於 128,則用一字節存儲
    • 若是大於等於128,則每一個字段用兩個字節
  • 對於一字節存儲,最高位標記字段是否爲 NULL,若是爲 NULL,則最高位爲1,不然爲0. 剩下的 7 位用來存儲長度,因此最可能是 127
  • 對於兩字節存儲,最高位仍是標記字段是否爲NULL第二位標記這條記錄是否在同一頁,若是在則爲0,若是不在則爲1,這其實就涉及到了後面要說的溢出頁。剩下的 14 位表示長度,因此最可能是 16383

來推算一下第一行的全部字段長度列表:spa

因爲第一行實際存儲的長度超過了128,因此須要兩字節。第一列到最後一列的長度,分別是:隱藏列DB_ROW_ID-6字節,隱藏列DB_TRX_ID-6字節,隱藏列DB_ROLL_PTR-7字節,列數據id-int-固定8字節,列數據score-double-固定8字節,列數據name-char-固定4字節,列數據content-varchar-變長8字節,列數據extra-varchar-變長14字節,large_content-變長130字節。轉換成偏移後爲:0x06,0x0c,0x13,0x1b,0x23,0x27,0x2f,0x3f,0xc1。變成兩字節,倒序過來就是:00 c1 00 3f 00 2f 00 27 00 23 00 1b 00 13 00 0c 00 063d

對於第三行,包含了 NULL 列,記錄長度小於 128,用一字節存儲。。第一列到最後一列的長度,分別是:隱藏列DB_ROW_ID-6字節,隱藏列DB_TRX_ID-6字節,隱藏列DB_ROLL_PTR-7字節,列數據id-int-固定8字節,列數據score-double-固定8字節,列數據name-char-固定4字節,列數據content-varchar-變長0字節,列數據extra-varchar-變長0字節,large_content-變長0字節。轉換成偏移後爲:0x06,0x0c,0x13,0x1b,0x23,0x27,0x27,0x27,0x27。因爲第一列和最後三列爲 NULL,因此將 0x1b,最後三個 0x27,0x27,0x27 的最高位設置爲1,變成 0x9b,0xa7,0xa7,0xa7.倒序過來就是:a7 a7 a7 27 23 9b 13 0c 06

Redundant - 記錄頭信息

Redundant 行格式的記錄頭(48位)信息比 Compact 的(40位)多了:

名稱 大小(bits) 描述
無用位 2 目前沒用到
deleted_flag 1 記錄是否被刪除
min_rec_flag 1 B+樹中非葉子節點最小記錄標記
n_owned 4 該記錄對應槽所擁有記錄數量
heap_no 13 該記錄在堆中的序號,也能夠理解爲在堆中的位置信息
n_field 10 該記錄的列數量,範圍從1到1023
1byte_offs_flag 1 1表明每一個字段長度爲1字節,0表明2字節
next_record pointer 16 頁中下一條記錄的相對位置

Redundant 行格式的記錄頭與 Compact 行格式的記錄頭的區別就是少了record_type位,多了n_field1byte_offs_flag這兩個。

n_field用來表示該記錄的列數量,範圍從1到1023。這裏的每一行都是 9 列,因此n_field都是9,也就是00000010011byte_offs_flag用來表示字段長度列表每一列佔用的字節數,1表明每一個字段長度爲1字節,0表明2字節。這裏只有第一行爲兩字節,因此第一行的這一位爲0

第一行記錄頭信息:00 00 10 12 01 65 
轉換爲2進制:00000000 00000000 00010000 00010010 00000001 01100101
n_field:000 0001001
1byte_offs_flag:0

第二行記錄頭信息:00 00 18 13 01 a8 
轉換爲2進制:00000000 00000000 00011000 00010011 00000001 10101000
n_field:000 0001001
1byte_offs_flag:1

第三行記錄頭信息:00 00 00 13 01 de
轉換爲2進制:00000000 00000000 00011000 00010011 00000001 11011110
n_field:000 0001001
1byte_offs_flag:1

第四行記錄頭信息:00 00 28 13 02 18 
轉換爲2進制:00000000 00000000 00101000 00010011 00000010 00011000
n_field:000 0001001
1byte_offs_flag:1

第四行記錄頭信息:00 00 30 13 00 74 
轉換爲2進制:00000000 00000000 00110000 00010011 00000000 01110100
n_field:000 0001001
1byte_offs_flag:1

複製代碼

Redundant - 具體列記錄存儲與 Compact 區別

1. 對 NULL 值的處理

對於 NULL,不像 Compact 那樣有 NULL 值列表,僅在字段長度列表的每一個字段長度最高位標記 1 表示這個字段爲 NULL。

同時對於定長字段,還會佔用相同長度的字節空間,每一個字節都填充上 00,例如第三,四行:

全部字段長度列表:a7 a7 a7 27 23 9b 13 0c 06 
記錄頭信息:00 00 00 13 01 de 
隱藏列DB_ROW_ID:00 00 00 00 09 02 
隱藏列DB_TRX_ID:00 00 00 03 cb 0e 
隱藏列DB_ROLL_PTR:ac 00 00 01 00 01 10 
列數據id(null):00 00 00 00 00 00 00 00 
列數據score(-669.996):87 16 d9 ce f7 ef 84 c0 
列數據name(aa):61 61 20 20 

全部字段長度列表:ab 2b 28 a7 a3 1b 13 0c 06 
記錄頭信息:00 00 28 13 02 18 
隱藏列DB_ROW_ID:00 00 00 00 09 03 
隱藏列DB_TRX_ID:00 00 00 03 cb 0f 
隱藏列DB_ROLL_PTR:ad 00 00 01 21 01 10 
列數據id(2048):80 00 00 00 00 00 08 00 
列數據score(null):00 00 00 00 00 00 00 00 
列數據name(null):00 00 00 00 
列數據content(c):63 
列數據extra(jun):6a 75 6e 
複製代碼

bigint 爲空時,填充了8個字節的 0x00。double 爲空時,填充了8個字節的 0x00。char(4) 爲空時,填充了4個字節的 0x00. 這樣,對於這些定長字段的修改,不管是從 NULL 改爲非 NULL 仍是從非 NULL 改爲 NULL,或者更新爲不一樣長度(可是在原始限制內),都不用將原有記錄標記爲刪除,以後再尋找新的空間重建更新後的記錄了,直接在原有記錄上面修改。對於 Compact,從 NULL 改爲非 NULL 仍是從非 NULL 改爲 NULL,是須要這種麻煩的更新方式的,由於 NULL 不佔用空間。

對於可變長度字段,Redundant 和 Compact 是相同的,爲 NULL 不佔用空間。只要改變長度,就會將原有記錄標記爲刪除,以後再尋找新的空間重建更新後的記錄

2. CHAR 類型存儲

不管字段是否爲 NULL,或者長度是多少,char(M) 都會佔用 M * 字節編碼最大長度那麼多字節。爲 NULL 的話,填充的是 0x00,不爲 NULL,長度不夠的狀況下,末尾補充 0x20.

例如上面的第四行:

列數據name(null):00 00 00 00 
複製代碼

還有第二行:

列數據name(zhx):7a 68 78 20 
複製代碼

咱們將 name 的編碼修改成 utf-8:

ALTER TABLE `record_test_2` 
MODIFY COLUMN `name` char(4) CHARACTER SET utf8 NULL DEFAULT NULL AFTER `score`;
複製代碼

再來看第四行的數據,變成了:

列數據name(null):00 00 00 00 00 00 00 00 00 00 00 00
複製代碼

由於 utf8 最大字節佔用爲3字節,因此這裏佔用 12字節。同理,第二行:

列數據name(zhx):7a 68 78 20 20 20 20 20 20 20 20 20
複製代碼

對於不一樣編碼的處理,Compact 和 Redundant 有明顯的區別,Compact 不會佔用那麼多字節,而是在某些狀況下像 varchar 同樣處理:

  • NULL 仍是不佔用空間
  • 字段全部字符佔用1字節,則按照1字節大小填充末尾的 0x20
  • 若是有其餘不一樣字節長度的字符,則按照實際佔用字節大小存儲,不補充末尾的 20

舉個例子,將上一節的 Compact 行格式的表,name 這一列修改編碼爲 utf8,同時修改數據:

ALTER TABLE `record_test_1` 
MODIFY COLUMN `name` char(4) CHARACTER SET utf8 NULL DEFAULT NULL AFTER `score`;
update `record_test_1` set name = "咱們" where id = 2048;
複製代碼

來看 id 爲 2048 的數據,變成了:

列數據name(咱們):e6 88 91 e4 bb ac
複製代碼

和 varchar 同樣,佔用 6 字節,正好是存儲數據的大小。

其餘行的數據存儲不變,例如:

列數據name(zhx):7a 68 78 20 複製代碼
相關文章
相關標籤/搜索