本文基于 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');

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

所有字段长度列表: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

第一行记录头信息: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原理 - InnoDB引擎 - 行记录存储 - Redundant行格式相关推荐

  1. MySQL原理 - InnoDB引擎 - 行记录存储 - Compact 行格式

    MySQL 服务器上负责对表中数据的读取和写入工作的部分是存储引擎,比如 InnoDB.MyISAM.Memory 等等,不同的存储引擎一般是由不同的人为实现不同的特性而开发的,目前OLTP业务的表如 ...

  2. Mysql技术-innodb引擎-笔记

    第2章 InnoDB存储引擎 2.3 InnoDB体系架构 InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作: 维护所有进程/线程需要访问的多个内部数据结构. 缓 ...

  3. mysql 5.7 io 性能 aio_深入理解MySQL的InnoDB引擎

    在MySQL中的引擎一文中说了,我们在几乎所有的情况下其实用的都是InnoDB引擎,这里我们就重点再看一下这个引擎,包括他的存储结构,线程模型和数据文件. 我们可以通过show engine inno ...

  4. MySQL中InnoDB引擎对索引的扩展

    摘要:InnoDB引擎对索引的扩展,自动追加主键值及其对执行计划的影响. MySQL中,使用InnoDB引擎的每个表,创建的普通索引(即非主键索引),都会同时保存主键的值. 比如语句 CREATE T ...

  5. mysql innodb引擎丢失_【MySQL】InnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据...

    注意!此方法只适用于innodb_file_per_table独立表空间的InnoDB实例. 此种方法可以恢复ibdata文件被误删.被恶意修改,没有从库和备份数据的情况下的数据恢复,不能保证数据库所 ...

  6. Mysql (InnoDB引擎)聚集索引和辅助索引

    聚集索引: InnoDB存储引擎表是索引组织表,即按照主键的顺序存储数据.  聚集索引(clustered index)就是按照每张表的主键构造一棵B+树,树中的叶子节点存放着表中的行记录数据,因此, ...

  7. mysql的引双向链表_一分钟掌握MySQL的InnoDB引擎B+树索引

    MySQL的InnoDB索引结构采用B+树,B+树什么概念呢,二叉树大家都知道,我们都清楚随着叶子结点的不断增加,二叉树的高度不断增加,查找某一个节点耗时就会增加,性能就会不断降低,B+树就是解决这个 ...

  8. CentOS上的安装和配置MYSQL 支持 INNODB引擎

    安装MYSQL数据库,详细参考: http://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html 简而言之,就是: 1) ...

  9. mysql中innodb的工作原理_解读MySQL的InnoDB引擎日志工作原理

    当你使用UPDATE, INSERT, DELETE语句更新数据的时候,你就改变了两个地方的数据:log buffer和data buffers.Buffers是固定长度的内存块,通常是512字节. ...

  10. MySQL的InnoDB引擎是如何解决幻读的?

    目录 幻读原因 InnoDB 的三种行锁 InnoDB 的解决方案 总结 面试题 在 MySQL 中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,只要提升隔离级别到串行化即可解决幻读问题 ...

最新文章

  1. Android--查找程序根目录下所有文件/Java IO操作
  2. C语言 变量 函数 (类型、作用域、生命周期、存储位置)
  3. opencv进阶学习笔记11:cannny边缘检测,直线检测,圆检测
  4. MapReduce-流量统计求和-排序-FlowBean编写
  5. 扛并发主力军,引入应用层缓存
  6. 助推曲烟数字化转型升级,开展生产业务数字化
  7. 字节跳动正秘密研发手机 网友:字节锤子手机真来了?
  8. 设计模式C++实现——工厂模式
  9. python字典长度可变吗_[python] 根据字典中的信息生成列表,每次都会额外变长。...
  10. ECNU·AntNLP主页船新上线!
  11. Windows验证字符串
  12. 配置xml文件来实现FlightGear通信,接收与发送数据
  13. 玉禾田环境金蝶云ERP操作手册
  14. Oracle 数据库实例介绍
  15. 人脸识别(三) 摄像头中的人脸识别+LFW数据集测试
  16. 科研入门 | 会议、期刊、出版社、数据库等常识
  17. 系统之家U盘 win10默认网关是什么
  18. 【时间序列】ICML 2020 时间序列相关论文总结(附原文源码)
  19. scylla_Scylla评论:Apache Cassandra增压
  20. 开发框架-.Net:Learun(力软敏捷开发)

热门文章

  1. 【渝粤教育】 国家开放大学2020年春季 1054流通概论 参考试题
  2. 关于WireShark跟随数据流后entire conversation显示的字节数分析
  3. java自行车s码适合身高_选购单车时,身高和尺寸对应表
  4. Delphi中小试Opencv--图像差异对比(大家来找茬辅助实现cvAbsDiff函数的使用)
  5. C#(unity/新手向)游戏暂停按钮(实现点击切换按钮文字:暂停/继续)
  6. ArcGIS 遥感图像分类—随机树和最大似然分类器
  7. windows7副本不是正版问题
  8. c语言计算机结题报告怎么写,结题报告格式及如何写结题报告
  9. 终于得空,写两句了......
  10. 微信公众号添加html,网站中增加微信公众账号链接的方法