前言
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式被称为行格式或者记录格式,今天我们就来介绍一下InnoDB行存储格式。希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。
基本操作
一行记录可以以不同的格式存在InnoDB中,行格式分别是compact、redundant、dynamic和compressed行格式。可以在创建或修改的语句中指定行格式:
-- 创建数据表时,显示指定行格式CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称;
-- 创建数据表时,修改行格式
ALTER TABLE 表名 ROW_FORMAT=行格式名称;
-- 查看某数据表的行格式
show table status from 数据库名 like '<数据表名>';
Compact行格式
Compact行格式是MySQL5.0中引入的,其目标是为了更高效的存储数据记录。其存储结构示意图如下:
从图中我们可以看出来,一条完整的记录其实可以被分为记录的额外信息和记录的真实数据两部分。
记录的额外信息
这部分信息是为了描述这条记录而不是额外添加的一些信息,这些额外信息分为三部分,分别是变长字段长度列表、NULL值列表和记录头信息;
变长字段的长度列表
MySQL支持一些变长的数据类型,如Varchar,变长字段中存储多少字节的数据是不固定的,所以InnoDB在存储数据的时候,会把这些数据占用的真实字节数也保存下来,也就是变长字段时占用了两部分空间来存储的:
- 真实的数据内容
- 占用的字节数
在Compact行格式中,把所有的变长字段锁占用的字节数按逆序排放在变长字段字节数列表中。
另外,变长字段的长度列表不是一定存在的,如表中没有变长类型的字段,或者该记录中所有的变长字段值均为NULL。
举例说明:
如图数据a、b、c三个字段所占用的字节数分别是1、2、5;InnoDB会把所占用的字节数逆序排放,如果用16进制来表示变长字段所占用的字节数就是这样的效果了:
NULL值列表
表中的某些列可能存储NULL值,如果把这些NULL值都放到记录的真实数据中存储则显的比较浪费空间,所以Compact行格式把这些值为NULL的列统一管理起来,存储到NULL值列表中。
- 如果表中有字段允许为NULL,InnoDB就会开辟一块空间来标识每个字段实际存储的数据是不是NULL,如果表中的字段都不允许为NULL,则NULL值列表也就不存在了。
- 每个允许存储NULL的列对应一个二进制位,二进制位按照列的逆序排列,二进制位表示的意义如下:
- 二进制位的值为1时,代表该列的值为NULL。
- 二进制位的值为0时,代表该列的值不为NULL。
我们通过一个示例看一下:
有一张表,表中有三个字段a、b、c;允许为NULL,插入两条记录,如下图:
我们先来看第一条数据,三个字段存储的实际数据都不为NULL,所以用二进制来表示是这样的:
但是InnoDB是用整数字节的二进制位来表示NULL值列表的,不足8位的要在高位补0,最终用二进制来表示如下:
第二条数据,其中a和b两个字段存储的实际数据都是NULL,用二进制如何来表示:
高位补0后:
PS:如果一个表中有9个允许为NULL的列,那这个记录的NULL值列表部分就需要2个字节来表示。
记录头信息
记录头信息用于描述该记录的,它是由固定的5个字节组成,即40个二进制位,不同的位代表不同的意思;
- 预留位1、2:暂未使用
- delete_mask:标记该记录是否被删除
- min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记
- n_owned:当前记录拥有的记录数
- heap_no:当前记录在记录堆中的位置信息
- record_type:当前记录类型;0-普通记录,1-B+树非叶子节点记录(即所谓的目录项记录),2-最小记录,3-最大记录
- next_record:下一条记录的相对位置
记录的数据内容
记录的真实数据除了自定义的列的数据以外,MySQL还会为每条记录默认的添加一些列(也称为隐藏列),具体的列如下:
列名
是否必须
占用空间
描述
DB_ROW_ID
否
6字节
行ID,唯一标识一条记录
DB_TRX_ID
是
6字节
事务ID
DB_ROLL_PTR
是
7字节
回滚指针
当用户未指定数据表的主键时,MySQL会选择非NULL的Unique列作为主键,而如果非NULL的Unique列也没有,这个时候MySQL就会向数据表添加DB_ROW_ID字段用来作为主键。
注意:记录的数据内容不包括字段值为NULL的数据内容。
Redundant行格式
redundant行格式是MySQL5.0之前的一种旧的格式,其结构与compact行格式大体还是比较相似的。在这里只简单了解一下就可以。
字段长度偏移列表
其通过字段长度偏移列表存储记录中所有列(包括隐藏列)的长度信息。首先计算记录中各字段的长度信息,然后再顺序计算长度的累计值,最后再按数据表各字段的顺序逆序排列,即为实际存储的字段长度偏移列表。反过来如果期望获取记录中某字段的长度信息,只需计算两个相邻长度累计值的差值即可。
记录头信息
记录头信息用于描述该条记录,其固定为6个字节,即48位。其定义如下:
- 预留位1、2:暂未使用
- delete_mask:标记该记录是否被删除
- min_rec_mask:B+树的每层非叶子节点中的最小记录的标志位
- n_owned:当前记录拥有的记录数
- heap_no:当前记录在记录堆中的位置信息
- n_field:表示记录中列的数量
- 1byte_offs_flag:标识字段长度偏移列表中各列的偏移量使用的字节数。0:意为每个偏移量均使用2个字节表示;1:意为每个偏移量均使用1个字节表示
- next_record:下一条记录的相对位置
记录的数据内容
记录的真实数据除了自定义的列的数据以外也存在隐藏列,这里就不一一介绍了,与Compact不同的是无论是使用变长字符集还是使用定长字符集,char(M)类型的字段总是占用M x Maxlen个字节的空间,字段值所占空间的字节数不足则同样会使用 空格字符(空格字符在ascii字符集下为0x20) 进行填充。
Dynamic、Compressed行格式
对于Dynamic、Compressed行格式而言,其和compact行格式比较相似。不同的在于,对待处理行溢出的处理及策略,Dynamic、Compressed行格式会把记录中数据量过大的字段值全部存储到溢出页中,而不会在该记录的数据内容的相应字段处存储该字段值前768个字节的数据了。而compressed相比较dynamic行格式来说,前者会使用压缩算法对所有页面(自然也包括溢出页)进行压缩以减少存储占用。
小结
我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式被称为行格式或者记录格式,行格式分别是compact、redundant、dynamic和compressed行格式。