MySQL原理 – InnoDB引擎 – 行记录存储 – Redundant行格式[亲测有效]

MySQL原理 – InnoDB引擎 – 行记录存储 – Redundant行格式[亲测有效]本文基于 MySQL 8 在上一篇:MySQL原理 – InnoDB引擎 – 行记录存储 – Compact格式 中,我们介绍了什么是 InnoDB 行记录存储以及 Compact 行格式,在这一篇…

MySQL原理 - InnoDB引擎 - 行记录存储 - Redundant行格式

本文基于 MySQL 8

上一篇:MySQL原理 – InnoDB引擎 – 行记录存储 – Compact格式 中,我们介绍了什么是 InnoDB 行记录存储以及 Compact 行格式,在这一篇中,我们继续介绍其他三种行格式。

Redundant 行格式

这个是最古老的,最简单粗暴的行格式了,现在基本上已经不用了,因为占用空间最多,从而导致内存碎片化最严重,是最低效的行格式了(针对现在varchar字段使用的更多,而对于 varchar 字段改变长度的更新大部分情况下就是将原有行的数据标记为已删除,然后在其他空间足够的地方新建记录,Redundant 顾名思义,占用空间更多,所以碎片化,空间浪费会更严重)。

MySQL官网的 Internal Mannual 给出的行格式示例,其实就是 Redundant 格式的: InnoDB Record High-Altitude Picture

创建一个和上一篇中的示例一样的表,插入相同的数据:

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

代码100分

代码100分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");

我们来直接看底层存储的数据是什么样子的:

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 值列表。这个字段长度列表的格式是:

  • 记录所有字段的长度偏移,包括隐藏列。偏移就是,第一个字段长度为 a,第二个字段长度为 b,那么列表中第一个字段就是 a,第二个字段就是 a + b。
  • 所有字段倒序排列

对于长度存储,是一字节还是两字节,以及存储的内容,Redundant 的规则比较特殊:

  • 根据整行记录的长度决定,到底每个字段用一个字节还是两个字节,每个字段用一个字节还是两个字节,在记录头信息里面有标记
    • 如果整行长度小于 128,则用一字节存储
    • 如果大于等于128,则每个字段用两个字节
  • 对于一字节存储,最高位标记字段是否为 NULL,如果为 NULL,则最高位为1,否则为0. 剩下的 7 位用来存储长度,所以最多是 127
  • 对于两字节存储,最高位还是标记字段是否为NULL第二位标记这条记录是否在同一页,如果在则为0,如果不在则为1,这其实就涉及到了后面要说的溢出页。剩下的 14 位表示长度,所以最多是 16383

来推算一下第一行的所有字段长度列表:

由于第一行实际存储的长度超过了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

对于第三行,包含了 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

代码100分第一行记录头信息: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 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/7492.html

(0)
上一篇 2023-03-19
下一篇 2023-03-19

相关推荐

  • 使用Python创建自我意识的Tulpa

    使用Python创建自我意识的TulpaTulpa起源于西藏佛教,意为“创造的东西”,指通过冥想和意识投射创造出来的意识体。Tulpa会在人脑中形成一种自主思考与行动的“分身”,具有独立思考的能力,可以与创造它的人进行交流和互动。

    2024-01-16
    111
  • 用Python的os.path.basename函数获取文件名

    用Python的os.path.basename函数获取文件名 在Python中,我们可以使用os.path.basename函数获取文件路径中的文件名部分,该函数用于获取文件的基本名称(字符串中最后一个反斜杠以后的部分),并将其作为字符串返回。如果路径以反斜杠结尾,则返回前一个部分。该函数可以应用于多种操作系统,如Windows,Linux,Unix等。使用该函数时,需要导入os模块。

    2023-12-10
    113
  • Python实现矩阵乘法

    Python实现矩阵乘法矩阵乘法是线性代数中的重要概念,对于Python工程师来说,熟练掌握矩阵乘法的方法是非常有必要的。Python在实现矩阵乘法时,可以通过NumPy库中的dot函数来进行计算。该函数可以接受2个ndarray型的参数,返回它们的矩阵乘积。

    2024-06-24
    48
  • CI查询构造器类(查询&生成查询结果)

    CI查询构造器类(查询&生成查询结果)CodeIgniter 提供了查询构造器类,查询构造器允许你使用较少的代码来在数据库中 获取、新增或更新数据。有时只需要一两行代码就能完成数据库操作。CodeIgniter 并不需要为每个数据表提供…

    2023-01-24
    146
  • SQL实用技巧:如何判断一个值是否为数字的方法「建议收藏」

    SQL实用技巧:如何判断一个值是否为数字的方法「建议收藏」检测是不是数字型的数据, 两种方法 1. ISNUMERIC ( expression ) 2. PATINDEX ( ‘%pattern%‘&#16

    2022-12-17
    145
  • SQL语句分类_SQL数据库基本语句

    SQL语句分类_SQL数据库基本语句SQL(Struct Query Language):结构化查询语句。 分为以下六类: 1.DDL(Data Definition Language)数据定义语言:定义和管理数据对象,如数据库,数据…

    2023-03-20
    161
  • 理解共享锁和排它锁

    理解共享锁和排它锁1.共享锁 (lock in share mode) 1.1 概念 允许不同事务之前共享加锁读取,但不允许其它事务修改或者加入排他锁 如果有修改必须等待一个事务提交完成,才可以执行,容易出现死锁 1…

    2023-01-27
    159
  • 并发事务问题与事务隔离级别[通俗易懂]

    并发事务问题与事务隔离级别[通俗易懂]1.并发事务问题 1)脏读:一个事物读到另一个事务还没有提交的数据。 2)不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。 3)幻读:一个事务按照条件查询数据时,没有对

    2023-05-11
    143

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注