1. InnoDB页
**InnoDB**
读取数据的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,**InnoDB中页的大小一般为 **_**16**_** KB**
。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
2. InnoDB行格式
平时以记录为单位向表中插入数据,记录在磁盘中的存放方式被称为**行格式**
。InnoDB现有四种行格式:Compact
、Redundant
、Dynamic
和Compressed
。
2.1 SQL语句
-- 创建或修改表时指定行格式
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称
2.2 Compact行格式
2.2.1 变长字段长度列表
MySQL支持一些变长数据类型,VARCHAR(M)
、VARBINARY(M)
、各种TEXT
类型,各种BLOB
类型,把拥有这些数据类型的列称为**变长字段**
。
由于变长字段存储多少字节数不固定,所以需要把占用的字节数也存起来。所以这些变长字段占用的存储空间分为两部分:
- 真正的数据内容
- 占用的字节数
在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放。
变长字段长度列表中只存储值为 非NULL 的列内容占用的长度,值为 NULL 的列的长度是不储存的。
:::info
并不是所有记录都有这个**变长字段长度列表**
部分,比方说表中所有的列都不是变长的数据类型的话,这一部分就不需要有。
:::
2.2.2 null值列表
Compact
行格式把这些值为NULL的列统一管理起来,存储到**NULL值列表**
中。处理过程如下:
- 首先统计表中允许存储NULL的列有哪些。
- 如果表中没有允许存储 NULL 的列,则 NULL值列表 也不存在了,否则将每个允许存储NULL的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如下:
- 二进制位的值为1时,代表该列的值为NULL。
- 二进制位的值为0时,代表该列的值不为NULL。
- MySQL规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0。
2.2.3 记录头信息
由固定的5个字节组成。5个字节也就是40个二进制位,不同的位代表不同的意思,如图:
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 | 1 | 没有使用 |
预留位2 | 1 | 没有使用 |
delete_mask | 1 | 标记该记录是否被删除 |
min_rec_mask | 1 | B+树的每层非叶子节点中的最小记录都会添加该标记 |
n_owned | 4 | 表示当前记录拥有的记录数 |
heap_no | 13 | 表示当前记录在记录堆的位置信息 |
record_type | 3 | 表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录 |
next_record | 16 | 表示下一条记录的相对位置 |
2.2.4 记录的真实数据
记录的真实数据除自定义的列的数据外,每个记录默认会添加一些列(称为**隐藏列**
)。
列名 | 是否必须 | 占用空间 | 描述 |
---|---|---|---|
**DB_ROW_ID** |
否 | 6字节 | 行ID,唯一标识一条记录 |
**DB_TRX_ID** |
是 | 6字节 | 事务ID |
**DB_ROLL_PTR** |
是 | 7字节 | 回滚指针 |
:::info
这里需要提一下InnoDB表对主键的生成策略:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为rowid的隐藏列作为主键。所以我们从上表中可以看出:InnoDB存储引擎会为每条记录都添加 transaction_id 和 roll_pointer 这两个列,但是 row_id_ 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。这些隐藏列的值不用我们操心,InnoDB存储引擎会自己帮我们生成的。
:::
2.2.5 CHAR(M)
- 对于 CHAR(M) 类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。
- 变长字符集的CHAR(M)类型的列要求至少占用M个字节,而VARCHAR(M)却没有这个要求。比方说对于使用utf8字符集的CHAR(10)的列来说,该列存储的数据字节长度的范围是10~30个字节。即使我们向该列中存储一个空字符串也会占用10个字节,这是怕将来更新该列的值的字节长度大于原有值的字节长度而小于10个字节时,可以在该记录处直接更新,而不是在存储空间中重新分配一个新的记录空间,导致原有的记录空间成为所谓的碎片。
2.3 Redundant行格式
2.3.1 字段长度偏移列表
- Redundant行格式会把该条记录中所有列(包括隐藏列)的长度信息都按照逆序存储到字段长度偏移列表。
- 采用两个相邻数值的差值来计算各个列值的长度。
2.3.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 | 表示下一条记录的绝对位置 |