1.准备工作
在MySQL逻辑架构一文中我们了解了MySQL有关架构的内容,现在我们聚焦到存储引擎上。
MySQL 服务器上负责对表中数据的读取和写入工作的部分是存储引擎,而服务器又支持不同类型的存储引擎,比如InnoDB 、MyISAM 、Memory 啥的,不同的存储引擎一般是由不同的人为实现不同的特性而开发的,真实数据在不同存储引擎中存放的格式一般是不同的。
由于InnoDB 是MySQL 默认的存储引擎,也是我们最常用到的存储引擎,所以这里先只介绍使用InnoDB 作为存储引擎的数据存储结构。
2.InnoDB页简介
计算机底层
计算机操作数据是发生在内存上的,所以需要把磁盘中的数据加载到内存中,但是读写磁盘的速度非常慢,和内存读写差了几个数量级。
目的
加快计算机对数据的操作速度
做法
InnoDB 采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
这和操作系统进行IO时的策略是有类似的思想的
3.InnoDB行格式
在数据库操作范畴,我们常说一条(行)记录或者一行(条)数据。这里取记录这种说法,我们平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为行格式或者记录格式。可以这样理解,我们平常说的一条记录,通过设计好的某种存放方式进行加工,最后得到一条特定行格式的记录。
到现在为止有多种的行格式,分别是Compact 、Redundant 、Dynamic 和Compressed 行格式,这里只关注Compact。
可以在创建或修改表的语句中指定行格式:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称
4.COMPACT行格式
一条完整的记录其实可以被分为记录的额外信息和记录的真实数据两大部分。
4.1记录的额外信息
这部分信息是服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为3类,分别是
- 变长字段长度列表
- NULL值列表
- 记录头信息
变长字段长度列表
MySQL 支持一些变长的数据类型,比如VARCHAR(M)
、VARBINARY(M)
、各种TEXT 类型,各种BLOB 类型,把拥有这些数据类型的列称为变长字段,变长字段中存储多少字节的数据是不固定的,我们需要把这些数据占用的字节数也存起来,所以这些变长字段占用的存储空间分为两部分:
- 真正的数据内容
- 占用的字节数
在Compact 行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放,我们再次强调一遍,是逆序存放!
另外需要注意的一点是,变长字段长度列表中只存储值为 非NULL 的列内容占用的长度,值为 NULL 的列的长度是不储存的 。
也并不是所有记录都有这个 变长字段长度列表 部分,比方说表中所有的列都不是变长的数据类型的话,这一部分就不需要有。
NULL值列表
我们知道表中的某些列可能存储NULL 值,如果把这些NULL 值都放到记录的真实数据中存储会很占地方,所以Compact 行格式把这些值为NULL 的列统一管理起来,存储到NULL 值列表中,它的处理过程是这样的:
- 首先统计表中允许存储NULL 的列有哪些
- 如果表中没有允许存储 NULL 的列,则 NULL值列表 也不存在了,否则将每个允许存储NULL 的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如下:
- 二进制位的值为1 时,代表该列的值为NULL 。
- 二进制位的值为0 时,代表该列的值不为NULL 。
- MySQL 规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0 。
记录头信息
它是由固定的5 个字节组成。5 个字节也就是40 个二进制位,不同的位代表不同的意思,如图:
这些属性混个脸熟即可,之后具体问题具体分析。
4.2记录的真实数据
我们才只说完Compact行格式里的记录的额外信息,现在看下记录的真实数据。
其实MySQL 会为每个记录默认的添加一些列(也称为隐藏列),具体的列如下:
列名 | 是否必须 | 占用空间 | 描述 |
row_id | 否 | 6字节 | 行ID,唯一标识记录 |
transaction_id | 是 | 6字节 | 事务ID |
roll_pointer | 是 | 7字节 | 回滚指针 |
小贴士:
实际上这几个列的真正名称其实是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR,我们为了美才写成了row_id、transaction_id和roll_pointer。
小贴士:
实际上这几个列的真正名称其实是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR,我们为了美才写成了row_id、transaction_id和roll_pointer。
这里需要提一下InnoDB 表对主键的生成策略:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique 键作为主键,如果表中连Unique 键都没有定义的话,则InnoDB 会为表默认添加一个名为row_id 的隐藏列作为主键。所以我们从上表中可以看出:InnoDB存储引擎会为每条记录都添加 transaction_id和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。