本文基於 MySQL 8html
在上一篇:MySQL原理 - InnoDB引擎 - 行記錄存儲 - Compact格式 中,咱們介紹了什麼是 InnoDB 行記錄存儲以及 Compact 行格式,在這一篇中,咱們繼續介紹其餘三種行格式。mysql
這個是最古老的,最簡單粗暴的行格式了,如今基本上已經不用了,由於佔用空間最多,從而致使內存碎片化最嚴重,是最低效的行格式了(針對如今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
全部字段長度列表: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
複製代碼
不一樣於 Compact 行格式,Redundant 的開頭是全部字段長度列表,而不是變長字段列表 + NULL 值列表。這個字段長度列表的格式是:ui
對於長度存儲,是一字節仍是兩字節,以及存儲的內容,Redundant 的規則比較特殊:編碼
來推算一下第一行的全部字段長度列表: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 06
。3d
對於第三行,包含了 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 行格式的記錄頭(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_field
和1byte_offs_flag
這兩個。
n_field
用來表示該記錄的列數量,範圍從1到1023。這裏的每一行都是 9 列,因此n_field
都是9,也就是0000001001
。1byte_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
複製代碼
對於 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 不佔用空間。只要改變長度,就會將原有記錄標記爲刪除,以後再尋找新的空間重建更新後的記錄。
不管字段是否爲 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 同樣處理:
舉個例子,將上一節的 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 複製代碼