InnoDB是mysql数据库中最常用的存储引擎,对其底层进行研究有助于理解常见的一些数据库优化思路。本篇文章介绍一条记录是如何存储在磁盘上。
我们平时都是以记录为单位向表中插入数据的,这些记录在磁盘上的存放形式也被称为行格式或者记录格式。InnoDB存储引擎到现在为止设计了4种不同类型的行格式,
分别是COMPACT、REDUNDANT、DYNAMI、COMPRESSED。其中REDUNDANT是最早使用的数据格式,现在基本上不怎么使用。目前使用较多的是其它三种格式,这三种格式基本相似,只在处理变长字段上有不同。下文将首先介绍COMPACT格式,然后在以此对比来说明另外俩种格式。
COMPACT行格式
定义一张表,方便后面举例说明
1 | CREATE TABLE record_format_demo( |
插入俩条数据
1 | INSERT INTO record_format_demo |
COMPACT行格式的基本结构如下图
一共分成俩部分额外信息和记录的真实数据。额外信息是为了更好的管理记录。
额外信息
变长字段列表
MySQL 支持一些变长的数据类型,比如 VARCHAR(M)、 BINARY(M)、各种 TEXT 类型、各种 BLOB 类型.我们也可以把拥有这些数据类型的列称为变长字段。变长字段中存储多少字节的数据是不固定的,所以我们在存储真实数据的时候需要顺便把这些数
据占用的字节数也存起来。
record_format_demo表的cl c2 c4列都是VARCHAR类型的,也就是变长的数据类型,所以这 个列的值占用的存储空间字节数都需要保存在记录开头处。同时record_format_demo表采用的是ASCII编码,每个字符只需要一个字节编码。另外在存储长度的时候,是按照列的逆序进行存储的。因此对于记录 (‘aaaa’ , ‘bbb’ , “ CC’ , ‘d’),相应的变长字段存储内容是 01 03 04
(十六进制表示,真实存储没有空格)。
如果变长字段的内容占用的字节数比较多 可能就需要用 字节来表示。至于用字节还是字节来表示变长字段的真实数据占用的字节数,InnoDB 它的一套规则.为
了更好地表述清楚这个规则,我们引入 W、M、L这几个符号,先分别看看这些符号的意思
- W 表示最多需要多少个字节来表示字符
- M 表示最多能存储多少个字符,比如varchar(10),m=10
- L表示实际字段存储需要用到的长度
确定使用1字节还是2字节来表示一个变长字段的真实数据占用的字节数的规则就是这样。
- 如果W * M <= 255 ,那么使用1字节来表示真实数据占用的字节数.
- 如果大于255 ,则分为下面两种情况。如果L<127 ,则用1字节来表示真实数据占用的字节数。如果L >127 ,则用2字节来表示真实数据占用的字节数
此外变长字段只存储非NULL字段。
另外对于CHAR类型的字段,如果表采用的编码方式是变长的,比如UTF-8,则此字段就是变长字段,如果采用的是定长字段,比如Ascii,则此字段就是定长。另外还有一点需要注意,采用变长编码字符集的CHAR(M)类型的列要求至少占M个字节,VARCHAR却没有这个要求。比如对于使用utf8字符集、类型为CHAR(10)的列来说,该列存储的数据占用的字节长度的范围就是10 - 30 字节.即使我们向该列中存储一个空字符串也会占用10字节,这主要是希望在将来更新该列时, 在新值的字节长度大于 字节长度但不大于10个字节 ,可以在该记录处直接更新.而不是在存储空间中再重新分配一个新的记录空间,导致原有的记录空间成为所谓的碎片。
NULL值列表
NULL值列表存储比较耗费存储,因此对于NULL值字段,采用二进制的方式进行存储。
对于字段中允许NULL值的字段,按照列的顺序逆序进行排列。NULL值则用0表示,有值则用1表示。Mysql规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补
对于记录('eeee' , 'fff', NULL ,NULL)
对应的NULL值列表对应的二进制列表0000 0110
。
记录头信息
记录头信息由固定的5字节组成,用于描述记录的一些属性。主要的内容如下
可以知道有这些信息,具体用到的时候再回来看。
记录真实数据
表中除了存储真实的数据,还会为每个记录默认地添加一些列(也称为隐藏列)。
这里需要提一下InnoDB表的主键生成策略,优先使用用户自定义的主键作为主键,如果用户没有定义主键 则选取一个不允许存储NULL值的UNIQUE键作为主键,如果表中连不允许存储NULL值的UNIQUE键都没有定义,则会为表默认添加一个名为 row_id 隐藏列作为主键.
因此row_id是可选的,另外俩列和事务有关。
溢出列
对于某些列,存储的内容可能大于16kB,而InnoDB是以页为基本单位来管理存储空间的,一页的大小是16kB,所以当前页是不能存储这份内容,对于这样的列称之为溢出列。
COMPACT行格式中 ,对于占用存储空间非常多的列 ,在记录的真实数据处只会存储该列的部分数据,而把剩余的数据分散存储在几个其他的页中然后在记录的真实数据处用20字节存储指向这些页的地址(当然字节还包括分散在其他页面中的数据所占用的字节数) ,从而可以找到剩余数据所在的页。
如果某一列中的数据非常多,则在本记录的真实数据处只会存储该列前768字节的数据以及一个指向其他页的址,然后把剩下的数据存放到其他页中。这些存储768字节之外的数据的页面也称为溢出页.
例子如下:
DYNAMIC 行格式和 COMPRESSED 行格式
下面来看另外两个行格式DYNAMIC行格式和 COMPRSSED行格式。这两个行格式与 COMPACT行格式挺像,只不过在处理溢出列的数据时有点儿分歧 它们不会在记录的真实数据处存储该溢出列真实数据的前768字节,而是把该列的所有真实数据都存储到溢出页中,只在记录的真实数据处存储20节大小的指向溢出页的地址(当然,这 20 字节还包括真实数据占用的字节数)。COMPRESSED行格式不同DYNAMIC行格式的一点是 COMPRESSED行格式会采用压缩算法进行压缩。
参考
- Mysql是怎样运行的