这一章解决的问题是:MySQL 是如何存储数据的?存在哪里,以怎么样的格式存储。
存储引擎负责读取、写入数据。这里介绍 InnoDB 存储引擎的记录存储结构。
InnoDB 存储引擎在读取数据时并不是一条一条的从磁盘中读取,而是一次性读取一页,一页是 16 KB。也就是在一般情况下,一次最少从磁盘读取 16 KB 的内容到内存中,一次最少把 16KB 的内容刷新到磁盘中。
指定行格式
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称;ALTER TABLE 表名 ROW_FORMAT=行格式名称;
CREATE TABLE record_format_demo (c1 varchar(10),c2 varchar(20) NOT NULL,c3 char(10),c4 varchar(10))CHARSET=ascii ROW_FORMAT=COMPACT;INSERT INTO record_format_demo(c1, c2, c3, c4) values ('aaaa', 'bbb', 'cc',' d'),('eeee', 'fff', null, null);
COMPACT 行格式
完整记录
- 记录的额外信息
- 变长字段长度列表
- MySQL 支持一些变长的数据结构,例如 VARCHAR(M)、VARBINARY(M)、各种 TEXT 类型、各种 BLOB 类型。我们可以把拥有这些数据结构的列称为变长字段,变长字段存储多少字段是不固定的,所以我们需要存储字段真实占用的字节数。所有变长字段占用的字节数都存放在记录的开头位置,形成一个变长字段长度列表。各变长字段的真实数据占用的字节数按照列的顺序逆序存放。
- NULL 值列表
- 记录头信息
- 变长字段长度列表
- 记录的真实信息
- 记录的额外信息
因为字符集是 ascii,每个字符用一个字节编码,所以 aaaa 需要用 4 个字节,用二进制表示 4 就是:0000 0100,为了方便书写,将其表示为 04,以此类推 bbb 和 d,然后逆序得到 010304。
- 如果一个字段的字节数大于 255(一个字节能表示的数最大是 255),用一个字节就无法表示了,就需要用到两个字节来表示。
- InnoDB 规定,如果这个字段最多占用的字节数小于等于 255,那么只用 1 个字节表示长度。如果最多占用的字节数大于 255,并且实际占用字节数小于等于 127,那么也是用一个字节表示长度。如果最多占用的字符数大于 255,并且实际占用字节数大于 127,那么就用两个字节表示。127 这个数字是 7 位二进制数的最大值,因为一个字节有 8 位,第一位需要用来判断这个字节用来表示实际长度,还是这个字节加上下一个字节表示长度,所以能使用的就只有 7 位。
- 如果某个字段占用的字节数特别多,InnoDB 可能把该字节的值的一部分数据存放到所谓的溢出页中。那么该字段只存储在本页面的长度,所以使用 2 字节就够了。尽管也是使用 2 个字节,但对于溢出字段而言,采用的方案不是单纯的将第一个二进制位置设置为 1。
- 另外需要注意的是,如果一个字段值为 NULL,那么将不存记录这个字段的长度。
并不是所有记录都有这个变长字段长度列表部分,如果表中所有的列都不是变长的数据类型,或者所有的列的值都是 NULL 的话,就不需要变长字段长度列表。
NULL 值列表
某些字段的值可能为 NULL,如果把 NULL 值都放到记录的真实数据中,会很占地方,这里在 NULL 值列表记录列的值是否为 NULL。
先统计哪些列允许存储 NULL
- 用二进制位记录某列是否为 NULL,1=NULL,0=不为NULL。同样是按照列的顺序逆序排列。NULL 值列表必须用整数个字节的位表示,比如一个字节的 8 位,2 个字节的 16 位,如果允许为 NULL 的列的数量不足,则在高位补 0,如果列的数量多的话,就用更多的字节来表示。
例子
第一行:00000 000
第二行:00000 110记录头信息
// 跳过
记录的真实数据
对于刚才建立的表而言,除了我们定义的几个列之外,MySQL 默认还添加了一些列。
| 列名 | 是否必须 | 占用空间 | 描述 |
|---|---|---|---|
| row_id | 否 | 6字节 | 行ID,唯一标识一条记录 |
| trx_id | 是 | 6字节 | 事务ID |
| roll_pointer | 是 | 7字节 | 回滚指针 |
- InnoDB 主键生成策略:使用用户自定义的主键 -> 选取一个不允许存储 NULL 值的 UNIQUE 键 -> 默认添加一个 row_id 键为主键
- 真实数据中,变长字段只占用真正需要的字节,而定长字段即使不需要用到那么多个字节,也会占用固定数量的字节,没有用到的字节用空格填充。
- 值为 NULL 的列是不存储的
变长字段需要存储实际占用的字节数,但是 CHAR(M) 在某些情况下也需要保存字段长度,不过保存的不是实际数据的字节长度,而是实际占用的字节长度。
如果一条记录的某个列存储的数据占用的字节数非常多,该列就可能成为溢出列。
- 对于溢出列,会在该列的位置存储一部分值,然后将其他值存储到其他页中。同时在该列的位置用 20 字节存储其他页的地址,以及在其他页占用的字节数。
-
其他行格式
REDUNDANT
- 5.0 之前的格式,已经非常古老,忽略
- DYNAMIC
- 在溢出列不存储数据,全部存储到溢出页中
- COMPRESSED
- 同上,并且使用压缩算法对页面进行压缩,以节省空空间

