大家好,众所周知,InnoDB是一个将表中数据存储到磁盘上的存储引擎。今天就来浅谈一下mysq数据库默认的存储引擎InnoDB是以什么样的形式将数据存放到磁盘上的。
平时我们看到的数据库中的数据库都是以一条条记录的形式展示的,例如下图所示。
这些记录在磁盘的存放形式也就是本文讲的记录存储结构称之为行格式。在InnoDB存储引擎中有4种不同类型的行格式,分别是COMPACT、REDUNDANT、DINAMIC和COMPRESSED。下面分别讲一下这四个行格式。
一、COMPACT行格式
如上图所示,COMPACT行格式分为记录的额外信息和记录的真实数据两部分。
1. 记录的额外信息
这部分信息是为了更好管理记录而添加的一些额外的信息。这些信息主要分为三部分:变长字段长度列表、NULL值列表、记录头信息。
(1)变长字段长度列表
mysql支持一些变长的数据类型,例如varchar()、varbinary()、各种text类型、各种blob类型等。在变长字段长度列表部分存储的就是这条记录中各个变长类型字段的真实数据占用的字节数(以列的顺序逆序存放),以第一张图第一条记录(喜羊羊)为例,假设每列是变长的数据类型,那么每个字段内容的字段长度如下所示:
列名 | 存储内容 | 内容长度(十进制) | 内容长度(十六进制) |
---|---|---|---|
id | 1 | 1 | 0x01 |
name | 喜羊羊 | 9 | 0x09 |
sex | 0 | 1 | 0x01 |
height | 172 | 3 | 0x03 |
weight | 150 | 3 | 0x03 |
所以这条记录在变长字段长度列表部分存储的信息就是0303010901。
注意:变长字段长度列表只存储值不为NULL的列的内容长度。所以第一张图第三条记录(美羊羊)由于height列和weight列都为NULL,所以第三条记录在变长字段长度列表部分存储的信息是010901。
(2)NULL值列表
顾名思义,NULL值列表存储的是一条记录中值为NULL的列,它的处理过程如下:
- 统计表中允许存储NULL的列有哪些。
- 若表中所有的列都不允许为空,那么NULL值列表就不存在了,否则将每个允许为空的列对应一个二进制位并按照列的顺序逆序排列,当二进制位的值为1时表示该列为NULL,当二进制位的值为0时表示该列不为NULL。
注意,这个二进制位必须用整数个字节表示,如果不是整数和字节,高位补0。
以第一张图第三条记录(美羊羊)为例,由于height列和weight列都为NULL,所以第三条记录(美羊羊)对应的NULL值列表如下图所示:
所以第三条记录(美羊羊)NULL值列表用十六进制表示就是0x18,在NULL值列表存储为18,而第一条记录(喜羊羊)没有为NULL的列,所以第一条记录(喜羊羊)NULL值列表用十六进制表示就是0x00,在NULL值列表存储为00。
(3)记录头信息
记录头信息由固定的5字节组成,用于描述记录的一些属性。详细信息如下所示:
记录头信息中各二进制位代表的详细信息:
名称 | 大小(位) | 描述 |
---|---|---|
预留位1 | 1 | 未使用 |
预留位2 | 1 | 未使用 |
delete_flag | 1 | 标记该记录是否删除 |
min_rec_flag | 1 | B+树每层非叶子节点中最小的目录项记录都会添加该标记 |
n_owned | 4 | 一个页面中的 记录会被分成好几个组,每个组“组长”的那条记录的n_owned会存储这个组中记录的条数,其余“组员”存0 |
beap_no | 13 | 表示当前记录在页面堆中的位置 |
record_type | 3 | 表示当前记录的类型 0-普通记录、1-B+树飞叶子节点的目录项记录、2-Infimum记录、3-表示Superemum记录 |
next_record | 16 | 表示下一条记录的相对位置 |
2. 记录的真实数据
记录的真实数据就是用来存储记录每列的真实数据。除了我们自己创建的列以外,mysql还会为每条记录添加几个隐藏列,具体隐藏列如下所示:
列名 | 是否必需 | 占用空间 | 描述 |
---|---|---|---|
row_id | 否 | 6字节 | 行ID |
trx_id | 是 | 6字节 | 事务ID |
roll_pointer | 是 | 7字节 | 回滚指针 |
InnoDB表优先使用用户定义的主键作为主键,如果用户没定义主键,则选取一个不允许为空的列作为主键,如果所有列都允许为空时,就会默认添加名为row_id的隐藏列作为主键。
二、REDUNDANT行格式
REDUNDANT行格式与COMPACT行格式主要区别就在于记录的额外信息部分,REDUNDANT行格式的记录的额外信息部分分为字段长度偏移列表和记录头信息两部分,这里主要讲一下这两部分。
1. 字段长度偏移列表
字段长度偏移列表存储的是该条记录的所有列信息(包括隐藏列),它采用两个相邻偏移量的差值来计算各个列值的长度并根据列顺序逆序进行存储。拿第一张图第一条记录(喜羊羊)来说:
第一列(row_id)长度为6个字节,所以存储为0x06。
第二列(trx_id)长度为6个字节,所以存储为0x0C(0x06+0x06)。
第三列(roll_pointer)长度为7个字节,所以存储为0x13(0x0C+0x07)。
第四列(id)长度为1个字节,所以存储为0x14(0x13+0x01)。
第五列(name)长度为9个字节,所以存储为0x1D(0x14+0x09)。
第六列(sex)长度为1个字节,所以存储为0x1E(0x1D+0x01)。
第七列(height)长度为3个字节,所以存储为0x21(0x1E+0x03)。
第八列(weight)长度为3个字节,所以存储为0x24(0x21+0x03)。
所以第一张图第一条记录(喜羊羊)的字段长度偏移列表为24211E1D14130C06。
2. 记录头信息
REDUNDANT行格式的记录头信息占用6个字节共计48个二进制位,具体意思如下所示:
记录头信息中各二进制位代表的详细信息
名称 | 大小(位) | 描述 |
---|---|---|
预留位1 | 1 | 未使用 |
预留位2 | 1 | 未使用 |
delete_flag | 1 | 标记该记录是否删除 |
min_rec_flag | 1 | B+树每层非叶子节点中最小的目录项记录都会添加该标记 |
n_owned | 4 | 一个页面中的 记录会被分成好几个组,每个组“组长”的那条记录的n_owned会存储这个组中记录的条数,其余“组员”存0 |
beap_no | 13 | 表示当前记录在页面堆中的位置 |
n_field | 10 | 表示记录中列的数量 |
1byte_offs_flag | 1 | 标记字段长度偏移量列表中每个列对应的偏移量是使用1个字节还是2个字节 |
next_record | 16 | 表示下一条记录的相对位置 |
与CPMPACT行格式不同的是:多了n_field和1byte_offs_flag两个属性但是少了record_type属性。
REDUNDANT行格式中NULL值的处理
REDUNDANT行格式没有NULL值列表,所以它将列对应的偏移量值的第一个比特位作为是否是NULL的依据,这个比特位称之为NULL比特位,如果该列对应的偏移量的NULL比特位为1,那么该列就为NULL,如果该列对应的偏移量的NULL比特位为0,那么该列就为不为NULL。
三、DYNAMIC行格式和COMPRESSED行格式
这两个行格式和CPMPACT行格式非常像,不同的点在于处理溢出列的数据的方式不同。CPMPACT行格式和REDUNDANT行格式在处理占用存储空间非常多的列时,会在记录的真实数据中存储该列的一部分数据,剩余的数据存储到其他的页中,然后在记录的真实数据部分用20字节存储指向这些页的地址。而DYNAMIC行格式和COMPRESSED行格式在处理溢出列时会把所有的真实数据都存储到其他页中。然后在记录的真实数据处只存储20字节指向溢出页的地址,其中COMPRESSED行格式会通过压缩算法对页面进行压缩处理。
到此,InnoDB记录存储结构介绍完毕,讲的不好的地方烦请大家多多指教。最后依旧是请各位老板有钱的捧个人场,没钱的也捧个人场,谢谢各位老板!