一文带你了解MySQL之InnoDB 记录结构【下篇】

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 上一篇文章讲了InnoDB有Compact、Redundant、Dynamic、Compressed4种⾏格式,其中主要讲了Compact行格式,有记录的额外信息和记录的真实数据两⼤部分组成,建议看上篇看上篇看上篇,重要的话说三遍【直通车】,别跳篇。今天我们主要理解剩余的三种行格式和行数据溢出

一、Redundant行格式

Redundant⾏格式是MySQL5.0之前⽤的⼀种⾏格式,也就是说它已经⾮常⽼了,但是本着学习的⻆度还是要提⼀下,⼤家了解下就可以了,Redundant⾏格式的如图:

微信图片_20230525223731.png


我们为了更好的学习,这边创建一张demo3的表,并且添加两条数据


mysql> create table demo3( c1 varchar(10), c2 varchar(10) not null, c3 char(10), c4 varchar(10), c5 varchar(1024)) charset=ascii row_format=Redundant;

Query OK, 0 rows affected (0.02 sec)

mysql> insert into demo3 values('aaaaa','bbbb','ccc','dd','e');

Query OK, 1 row affected (0.00 sec)

mysql> insert into demo3 values('eeeee','ffff',null,null,'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdeefghijklmnopqrstuvwxyz');

Query OK, 1 row affected (0.00 sec)

现在表中的记录就是这个样⼦的


mysql> select * from demo3;

+-------+------+------+------+------------------------------------------------------------------------------------------------------------------------------------+

| c1    | c2   | c3   | c4   | c5                                                                                                                                 |

+-------+------+------+------+------------------------------------------------------------------------------------------------------------------------------------+

| aaaaa | bbbb | ccc  | dd   | e                                                                                                                                  |

| eeeee | ffff | NULL | NULL | abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz |

+-------+------+------+------+------------------------------------------------------------------------------------------------------------------------------------+

2 rows in set (0.00 sec)


下边我们从各个⽅⾯看⼀下Redundant⾏格式有什么不同的地⽅。


1.1 字段长度偏移列表

Compact行格式的开头是变⻓字段⻓度列表,⽽Redundant行格式的开头是字段长度偏移列表,与变长字段长度列表有两处不同:


没了变长两个字,也就是说Redundant行格式会把该条记录中所有的列(包括隐藏列)的长度信息都按照逆序存储到字段长度偏移列表

多了偏移两个字,也就是说计算长度的方式跟Compact行格式不一样了,没有那么直观,它采用的相邻数的差值来计算各个列的长度。

如第一行数据,我们试着分析一下:


第一列row_id的长度是6个字节,也就是0x06

第二列trx_id的长度是6个字节,一共12个字节,也就是0x0c

第三列roll_pointer的长度是7个字节,一共19个字节,也就是0x13

第四列C1的长度是5个字节,一共24个字节,也就是0x18

第五列C2的长度是4个字节,一共28个字节,也就是0x1c

第六列C3的长度是10个字节,一共38个字节,也就是0x26

第七列C4的长度是2个字节,一共40个字节,也就是0x28

第八列C5的长度是1个字节,一共41个字节,也就是0x29


逆序排放的效果就是:29 28 26 1c 18 13 0c 06,填入数据如图

微信图片_20230525223838.png


我们直接打开demo3.ibd文件查看

微信图片_20230525223856.png


1.2 记录头信息

Redundant行格式的记录头信息6个字节,48个二进制位,这些二进制位代表的意思如下:


名称 ⼤⼩(单位:bit) 描述

预留位1 1 没有使⽤

预留位2 1 没有使⽤

delete_mask 1 标记该记录是否被删除

min_rec_mask 1 B+树的每层⾮叶⼦节点中的最⼩记录都会添加该标记

n_owned 4 表示当前记录拥有的记录数

heap_no 13 表示当前记录在记录堆的位置信息

n_field 10 表示记录中列的数量

1byte_offs_flag 1 标记字段长度偏移列表中每个列对应的偏移量是使用1字节还是2字节表示的

next_record 16 表示下⼀条记录的相对位置

第一行记录头信息是:00 00 10 11 00 ca


我们将上面的记录由十六进制转换为二进制试着分析一下


00000000 00000000 00010000 00010001 00000000 11001010


根据这些二进制数据,将数据按照记录头结构切分可以得到如下信息


预留位1:0

预留位2:0

delete_mask:0

min_rec_mask:0

n_owned:0000

heap_no:00000000 00010 (2)

n_field:000 0001000(8)

1byte_offs_flag:1(1)

next_record: 00000000 11001010(202)


与Compact行格式的记录头信息对比来看,有两处不同:


Redundant行格式多了n_field和1byte_offs_flag这两个属性;

Redundant行格式没有record_type这个属性。


1.3 byte_offs_flag的值是怎么选择的

我们前边说过每个列对应的偏移量可以占用1个字节或者2个字节来存储,那到底什么时候用1个字节,什么时候用2个字节呢?其实是根据该条Redundant行格式记录的真实数据占用的总大小来判断的:


当记录的真实数据占⽤的字节数小于等于127(⼗六进制0x7f,⼆进制01111111)时,每个列对应的偏移量占⽤1个字节


当记录的真实数据占⽤的字节数⼤于127,但不⼤于32767(⼗六进制0x7fff,⼆进制0111111111111111)时,每个列对应的偏移量占⽤2个字节


当记录的真实数据大于32767的情况时,此时的记录已经存放到了溢出页中,在本页中只保留前768个字节和20个字节的溢出页面地址(当然这20个字节中还记录了一些别的信息)。因为字段长度偏移列表处只需要记录每个列在本页面中的偏移就好了,所以每个列使用2个字节来存储偏移量就够了。


为了在解析记录时知道每个列的偏移量是使用1个字节还是2个字节表示的,Redundant行格式特意在记录头信息里放置了一个称之为1byte_offs_flag的属性:


当它的值为1时,表明使用1个字节存储

当它的值为0时,表明使用2个字节存储


1.4 Redundant行格式中NULL值的处理

因为Redundant行格式并没有NULL值列表,Redundant行格式在字段长度偏移列表中的各个列对应的偏移量处做了一些特殊处理 —— 将列对应的偏移量值的第一个比特位作为是否为NULL的依据,该比特位也可以被称之为NULL比特位。也就是说在解析一条记录的某个列时,首先看一下该列对应的偏移量的NULL比特位是不是为1,如果为1,那么该列的值就是NULL,否则不是NULL。


这也就解释了上边介绍为什么只要记录的真实数据大于127(十六进制0x7f,二进制01111111)时,就采用2个字节来表示一个列对应的偏移量,主要是第一个比特位是所谓的NULL比特位,用来标记该列的值是否为NULL。


但是还有一点要注意,对于值为NULL的列来说,该列的类型是否为定长类型决定了NULL值的实际存储方式:


如果存储NULL值的字段是定长类型的,比方说CHAR(M)数据类型的,则NULL值也将占用记录的真实数据部分,并把该字段对应的数据使用0x00字节填充。

如果该存储NULL值的字段是变长数据类型的,则不在记录的真实数据处占用任何存储空间。

除了以上的几点之外,Redundant行格式和Compact行格式还是大致相同的


我们试着分析一下第二条数据(真实数据占⽤的字节数大于127):


第一列row_id的长度是6个字节,也就是0x00 0x06

第二列trx_id的长度是6个字节,一共12个字节,也就是0x00 0x0c

第三列roll_pointer的长度是7个字节,一共19个字节,也就是0x00 0x13

第四列C1的长度是5个字节,一共24个字节,也就是0x00 0x18

第五列C2的长度是4个字节,一共28个字节,也就是0x00 0x1c

第六列C3的长度是10个字节,一共38个字节,也就是0x00 0x26,但是值为NULL,最高位标识1之后,也就是0x80 0x26

第七列C4的值为NULL,一共还是38个字节,也就是0x80 0x26

第八列C5的长度是130个字节,一共168个字节,也就是0x00 0xa8


我们直接打开demo3.ibd文件查看,是否正确:

微信图片_20230525223936.png

除了以上的几点之外,Redundant行格式和Compact行格式还是大致相同的。


1.5 CHAR(M)列的存储格式

我们知道Compact行格式在CHAR(M)类型的列中存储数据的时候还挺麻烦,分变长字符集和定长字符集的情况,而在Redundant行格式中十分干脆,不管该列使用的字符集是啥,只要是使用CHAR(M)类型,占用的真实数据空间就是该字符集表示一个字符最多需要的字节数和M的乘积。比方说使用utf8字符集的CHAR(10)类型的列占用的真实数据空间始终为30个字节,使用gbk字符集的CHAR(10)类型的列占用的真实数据空间始终为20个字节。由此可以看出来,使用Redundant行格式的CHAR(M)类型的列是不会产生碎片的。


二、行数据溢出

2.1 VARCHAR(M)最多能存储的数据

我们知道对于VARCHAR(M)类型的列最多可以占用65535个字节。其中的M代表该类型最多存储的字符数量,如果我们使用ascii字符集的话,一个字符就代表一个字节,我们看看VARCHAR(65535)是否可用


mysql> create table demo4 (c varchar(65535)) charset=ascii row_format=compact;

ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs


从报错信息里可以看出,MySQL对一条记录占用的最大存储空间是有限制的,除了BLOB或者TEXT类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节。所以MySQL服务器建议我们把存储类型改为TEXT或者BLOB的类型。这个65535个字节除了列本身的数据之外,还包括一些其他的数据(storage overhead),比如说我们为了存储一个VARCHAR(M)类型的列,其实需要占用3部分存储空间:


真实数据

真实数据占用字节的长度(可能占两位)

NULL值标识,如果该列没有NOT NULL属性(占一位),那么最多只能存储65532个字节数据

mysql> create table demo4 (c varchar(65532)) charset=ascii row_format=compact;

Query OK, 0 rows affected (0.06 sec)

如果VARCHAR(M)类型的列使用的不是ascii字符集,那M的最大取值取决于该字符集表示一个字符最多需要的字节数。在列的值允许为NULL的情况下,gbk字符集表示一个字符最多需要2个字节,那在该字符集下,M的最大取值就是32766(也就是:65532/2),也就是说最多能存储32766个字符;utf8字符集表示一个字符最多需要3个字节,那在该字符集下,M的最大取值就是21844,就是说最多能存储21844(也就是:65532/3)个字符。


2.2 记录中的数据太多产⽣的溢出

我们以ascii字符集的demo4表做个测试,插入一条数据:


mysql> insert into demo4 values(repeat('a',65532));

Query OK, 1 row affected (0.02 sec)


小提示:

REPEAT(‘a’, 65532)是一个函数调用,它表示生成一个把字符’a’重复65532次的字符串


前边说过,MySQL中磁盘和内存交互的基本单位是页,也就是说MySQL是以页为基本单位来管理存储空间的,我们的记录都会被分配到某个页中存储。而一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65532个字节,这样就可能造成一个页存放不了一条记录的尴尬情况


在Compact和Reduntant⾏格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址(当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从⽽可以找到剩余数据所在的页,如图所示:

微信图片_20230525224008.png


从图中可以看出来,对于Compact和Reduntant行格式来说,如果某一列中的数据非常多的话,在本记录的真实数据处只会存储该列的前768个字节的数据和一个指向其他页的地址,然后把剩下的数据存放到其他页中,这个过程也叫做页溢出,存储超出768字节的那些页面也被称为溢出页。


最后需要注意的是,不只是 VARCHAR(M)类型的列,其他的 TEXT、BLOB 类型的列在存储数据非常多的时候也会发生行溢出。


三、行溢出的临界点

那发生行溢出的临界点是什么呢?也就是说在列存储多少字节的数据时就会发生行溢出?


MySQL中规定一个页中至少存放两行记录,每个页除了存放我们的记录以外,也需要存储一些额外的信息,乱七八糟的额外信息加起来需要132个字节的空间(现在只要知道这个数字就好了),其他的空间都可以被用来存储记录。


每个记录需要的额外信息是27字节。


2个字节用于存储真实数据的长度

1个字节用于存储列是否是NULL值

5个字节大小的头信息

6个字节的DB_ROW_ID列

6个字节的DB_TRX_ID列

7个字节的DB_ROLL_PTR列

假设一个列中存储的数据字节数为n,MySQL规定如果该列不发生溢出的现象,就需要满足下边这个式子:


132 + 2×(27 + n) < 16384


求解这个式子得出的解是:n < 8099。也就是说如果一个列中存储的数据小于8099个字节,那么该列就不会成为溢出列,否则该列就需要成为溢出列。不过这个8099个字节的结论只是针对只有一个列的demo4表来说的,如果表中有多个列,那上边的式子和结论都需要改一改了,所以重点就是:你不用关注这个临界点是什么,只要知道如果我们一条记录的某个列中存储的数据占用的字节数非常多时,该列就可能成为溢出列。


四、DYNAMIC和COMPRESSED行格式

下边要介绍另外两个行格式,Dynamic和Compressed行格式,我现在使用的MySQL版本是8.0,它的默认行格式就是Dynamic


mysql> show variables like '%innodb_default_row_format%';

+---------------------------+---------+

| Variable_name             | Value   |

+---------------------------+---------+

| innodb_default_row_format | dynamic |

+---------------------------+---------+

1 row in set (0.04 sec)

mysql> set persist innodb_default_row_format = 'dynamic';

Query OK, 0 rows affected (0.01 sec)


这俩行格式和Compact行格式挺像,只不过在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。


Compressed行格式和Dynamic不同的一点是,Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。


总结

这两篇文章理论知识着实有点多,哈哈,不过好在工作中实际用的不是很多,大家知道有这些名词就好,因为在后面的MVCC的讲解中,我们还会用到本文的一些专业名词。InnoDB定义了4种行格式,每种行格式的记录结构是不同的,同时我们还学习了记录结构中对定长、变长的数据类型的存储方式以及行溢出等相关知识,下面我们就来做一个总结


页是MySQL中磁盘和内存交互的基本单位,也是MySQL是管理存储空间的基本单位。


InnoDB目前定义了4种行格式:


COMPACT行格式:

微信图片_20230525224051.png

REDUNDANT行格式:

微信图片_20230525224116.png


DYNAMIC和COMPRESSED行格式:


Dynamic和Compressed行格式类似于COMPACT行格式,在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址


COMPACT行格式对于CHAR(M)类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。


在列的值允许为NULL的情况下,gbk字符集下M的最大取值就是32766,utf8字符集下M的最大取值就是21844,这都是在表中只有一个字段的情况下说的,一定要记住一个行中的所有列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节!


当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出。Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址。


行溢出的临界点:如果一个列中存储的数据小于8099个字节,那么该列就不会成为溢出列,否则该列就需要成为溢出列。不过这个8099个字节的结论只是针对只有一个列的表来说的,如果表中有多个列,那结论就不一致了,所以重点就是:你不用关注这个临界点是什么,只要知道如果我们一条记录的某个列中存储的数据占用的字节数非常多时,该列就可能成为溢出列。


变长字符集的CHAR(M)类型的列要求至少占用M个字节,而VARCHAR(M)却没有这个要求。比方说对于使用utf8字符集的CHAR(10)的列来说,该列存储的数据字节长度的范围是10~30个字节。即使我们向该列中存储一个空字符串也会占用10个字节,这是怕将来更新该列的值的字节长度大于原有值的字节长度而小于10个字节时,可以在该记录处直接更新,而不是在存储空间中重新分配一个新的记录空间,导致原有的记录空间成为所谓的碎片


自己学完这部分知识的时候,很佩服本书作者的研究深度,虽然知识点多、难、杂,但是作者图文并茂的方式可以让我们很好的理解


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
11天前
|
存储 缓存 关系型数据库
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL的存储引擎是其核心组件之一,负责数据的存储、索引和检索。不同的存储引擎具有不同的功能和特性,可以根据业务需求 选择合适的引擎。本文详细介绍了MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案。
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
|
16天前
|
存储 关系型数据库 MySQL
MySQL存储引擎详述:InnoDB为何胜出?
MySQL 是最流行的开源关系型数据库之一,其存储引擎设计是其高效灵活的关键。InnoDB 作为默认存储引擎,支持事务、行级锁和外键约束,适用于高并发读写和数据完整性要求高的场景;而 MyISAM 不支持事务,适合读密集且对事务要求不高的应用。根据不同需求选择合适的存储引擎至关重要,官方推荐大多数场景使用 InnoDB。
62 7
|
26天前
|
存储 关系型数据库 MySQL
Mysql索引:深入理解InnoDb聚集索引与MyisAm非聚集索引
通过本文的介绍,希望您能深入理解InnoDB聚集索引与MyISAM非聚集索引的概念、结构和应用场景,从而在实际工作中灵活运用这些知识,优化数据库性能。
108 7
|
1月前
|
存储 关系型数据库 MySQL
MySQL引擎InnoDB和MyISAM的区别?
InnoDB是MySQL默认的事务型存储引擎,支持事务、行级锁、MVCC、在线热备份等特性,主索引为聚簇索引,适用于高并发、高可靠性的场景。MyISAM设计简单,支持压缩表、空间索引,但不支持事务和行级锁,适合读多写少、不要求事务的场景。
58 9
|
13天前
|
存储 Oracle 关系型数据库
数据库传奇:MySQL创世之父的两千金My、Maria
《数据库传奇:MySQL创世之父的两千金My、Maria》介绍了MySQL的发展历程及其分支MariaDB。MySQL由Michael Widenius等人于1994年创建,现归Oracle所有,广泛应用于阿里巴巴、腾讯等企业。2009年,Widenius因担心Oracle收购影响MySQL的开源性,创建了MariaDB,提供额外功能和改进。维基百科、Google等已逐步替换为MariaDB,以确保更好的性能和社区支持。掌握MariaDB作为备用方案,对未来发展至关重要。
39 3
|
13天前
|
安全 关系型数据库 MySQL
MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!
《MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!》介绍了MySQL中的三种关键日志:二进制日志(Binary Log)、重做日志(Redo Log)和撤销日志(Undo Log)。这些日志确保了数据库的ACID特性,即原子性、一致性、隔离性和持久性。Redo Log记录数据页的物理修改,保证事务持久性;Undo Log记录事务的逆操作,支持回滚和多版本并发控制(MVCC)。文章还详细对比了InnoDB和MyISAM存储引擎在事务支持、锁定机制、并发性等方面的差异,强调了InnoDB在高并发和事务处理中的优势。通过这些机制,MySQL能够在事务执行、崩溃和恢复过程中保持
42 3
|
13天前
|
SQL 关系型数据库 MySQL
数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog
《数据库灾难应对:MySQL误删除数据的救赎之道,技巧get起来!之binlog》介绍了如何利用MySQL的二进制日志(Binlog)恢复误删除的数据。主要内容包括: 1. **启用二进制日志**:在`my.cnf`中配置`log-bin`并重启MySQL服务。 2. **查看二进制日志文件**:使用`SHOW VARIABLES LIKE &#39;log_%&#39;;`和`SHOW MASTER STATUS;`命令获取当前日志文件及位置。 3. **创建数据备份**:确保在恢复前已有备份,以防意外。 4. **导出二进制日志为SQL语句**:使用`mysqlbinlog`
54 2
|
27天前
|
关系型数据库 MySQL 数据库
Python处理数据库:MySQL与SQLite详解 | python小知识
本文详细介绍了如何使用Python操作MySQL和SQLite数据库,包括安装必要的库、连接数据库、执行增删改查等基本操作,适合初学者快速上手。
184 15
|
20天前
|
SQL 关系型数据库 MySQL
数据库数据恢复—Mysql数据库表记录丢失的数据恢复方案
Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、Mysql数据库表中无任何数据或只有部分数据。 2、客户端无法查询到完整的信息。
|
27天前
|
关系型数据库 MySQL 数据库
数据库数据恢复—MYSQL数据库文件损坏的数据恢复案例
mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库。