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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 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
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');

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


微信图片_20220624184601.png


所有字段长度列表: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_flag1记录是否被删除min_rec_flag1B+树中非叶子节点最小记录标记n_owned4该记录对应槽所拥有记录数量heap_no13该记录在堆中的序号,也可以理解为在堆中的位置信息n_field10该记录的列数量,范围从1到10231byte_offs_flag11代表每个字段长度为1字节,0代表2字节next_record pointer16页中下一条记录的相对位置


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
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
30天前
|
存储 缓存 关系型数据库
详细解析MySQL中的innodb和myisam
总之,InnoDB和MyISAM各有千秋,选择合适的存储引擎应基于对应用程序特性的深入理解,以及对性能、数据完整性和可扩展性的综合考量。随着技术发展,InnoDB因其全面的功能和日益优化的性能,逐渐成为更广泛场景下的首选。然而,在特定条件下,MyISAM依然保留其独特的价值。
90 0
|
1月前
|
存储 关系型数据库 MySQL
Mysql行格式DYNAMIC和COMPACT区别
总之,选择哪种行格式取决于具体的应用场景,如数据类型分布、读写比例、存储与性能需求等。在处理大量文本或二进制数据且对存储空间敏感的应用中,DYNAMIC格式可能是更好的选择;而对于混合型数据且对读取性能有一定要求的场景,COMPACT格式可能更合适。在设计数据库时,评估这些因素并进行适当测试,可以帮助确定最适合的行格式。
74 0
|
3月前
|
监控 关系型数据库 MySQL
在Linux中,mysql的innodb如何定位锁问题?
在Linux中,mysql的innodb如何定位锁问题?
|
3月前
|
存储 SQL 关系型数据库
mysql百分数转小数点格式
在MySQL中,将百分数转换为小数点格式是一个简单直接的操作,可以通过基本的数学表达式和函数实现。无论是处理以字符串形式存储的百分数值,还是直接以数值形式表示的百分比,都可以通过适当的转换查询轻松实现这一目标。通过理解和应用这些基本的转换方法,可以有效地处理和分析数据库中的百分比数据。
39 5
|
3月前
|
SQL 存储 关系型数据库
"MySQL增列必锁表?揭秘InnoDB在线DDL,让你的数据库操作飞一般,性能无忧!"
【8月更文挑战第11天】在数据库领域,MySQL凭借其稳定高效的表现深受开发者喜爱。对于是否会在给数据表添加列时锁表的问题,MySQL的行为受版本、存储引擎等因素影响。从5.6版起,InnoDB支持在线DDL,可在改动表结构时保持表的可访问性,避免长时间锁表。而MyISAM等则需锁表完成操作。例如,在使用InnoDB的表上运行`ALTER TABLE users ADD COLUMN email VARCHAR(255);`时,通常不会完全锁表。虽然在线DDL提高了灵活性,但复杂操作或大表变更仍可能暂时影响性能。因此,进行结构变更前应评估其影响并择机执行。
68 6
|
4月前
|
存储 SQL 关系型数据库
(十三)MySQL引擎篇:半道出家的InnoDB为何能替换官方的MyISAM?
MySQL是一款支持拔插式引擎的数据库,在开发过程中你可以根据业务特性,从支持的诸多引擎中选择一款适合的,例如MyISAM、InnoDB、Merge、Memory(HEAP)、BDB(BerkeleyDB)、Example、Federated、Archive、CSV、Blackhole.....
|
5月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
|
5月前
|
存储 SQL 关系型数据库
【MySQL技术内幕】6.3-InnoDB中的锁
【MySQL技术内幕】6.3-InnoDB中的锁
196 57
|
5月前
|
存储 关系型数据库 MySQL
关系型数据库mysql的InnoDB
【6月更文挑战第17天】
42 3
|
4月前
|
存储 关系型数据库 MySQL
MySQL InnoDB存储引擎的优点有哪些?
上述提到的特性和优势使得InnoDB引擎非常适合那些要求高可靠性、高性能和事务支持的场景。在使用MySQL进行数据管理时,InnoDB通常是优先考虑的存储引擎选项。
156 0

推荐镜像

更多