MySQL 一行记录是怎么存储的?
MySQL 的数据存放在哪个文件?
MySQL 存储的行为是由存储引擎实现的,不同的存储引擎保存的文件自然也不同。 对于InnoDB
每个数据库以 database 为名的目录存放在mysql数据文件夹下
文件夹通过mysql命令行 show variables like 'datadir'; 查看,不同的安装方式不一定 一张数据库表的数据是保存在「表名字.ibd」的文件里的,这个文件也称为独占表空间文件。
表空间文件的结构是怎么样的?
1、段(segment)
表空间是由各个段(segment)组成的,段是由多个区(extent)组成的。段一般分为数据段、索引段和回滚段等。
- 索引段:存放 B + 树的非叶子节点的区的集合;
- 数据段:存放 B + 树的叶子节点的区的集合;
- 回滚段:存放的是回滚数据的区的集合,事务隔离的 MVCC 利用回滚段实现了多版本查询数据。
2、区(extent)
区的大小确实是 1MB(16KB 页 ×64)
- 当表数据量较小时,先按页分配;
- 超过半区后切换为按区分配,避免碎片化;
- 以顺序I/O的优势加速查询
3、页(page)
最小单元,默认每个页的大小为 16KB意味着数据库每次读写都是以 16KB 为单位的,一次最少从磁盘中读取 16K 的内容到内存中,一次最少把内存中的 16K 内容刷新到磁盘中。
4、行(row)
数据库表中的记录都是按行(row)进行存放的,每行记录根据不同的行格式,有不同的存储结构。
InnoDB 行格式有哪些?
InnoDB 提供了 4 种行格式,分别是 Redundant、Compact、Dynamic 和 Compressed 行格式。其中Redundant废弃,其余三个格式类似
Compact 行格长这样:

可以看到,一条完整的记录分为「记录的额外信息」和「记录的真实数据」两个部分。
记录的额外信息
记录的额外信息包含 3 个部分:变长字段长度列表、NULL 值列表、记录头信息。
1. 变长字段长度列表
只出现在数据表有变长字段的时候。
varchar(n) 和 char(n) 的区别是char 是定长的,varchar 是变长的。
要把数据占用的大小存起来,存到「变长字段长度列表」里面,读取数据的时候才能根据这个「变长字段长度列表」去读取对应长度的数据。TEXT、BLOB 等变长字段也是这么实现的
2. NULL 值列表
如果存在允许 NULL 值的列,则每个列对应一个二进制位(bit),二进制位按照列的顺序逆序排列。
- 二进制位的值为
1时,代表该列的值为 NULL。 - 二进制位的值为
0时,代表该列的值不为 NULL。
另外,NULL 值列表必须用整数个字节的位表示(1 字节 8 位),如果使用的二进制位个数不足整数个字节,则在字节的高位补 0。
当数据表的字段都定义成 NOT NULL 的时候,这时候表里的行格式就不会有 NULL 值列表了,所以建议这样设置节省至少1字节空间。(9个字段就需要两个字节空间)
3. 记录头信息
记录头信息中包含的内容很多,我就不一一列举了,这里说几个比较重要的:
delete_mask:标识此条数据是否被删除。
- delete 操作不会立即删除,只是将 delete_mask 标记为 1。响应更快一点。
- 对于MVCC而言,如果某条记录被删除了,但是因为这条记录物理上依旧存在,所以不会导致出现查询的记录出现减少(因为如果实际物理删除了,那么也就找不到这一行的trx_id是否在当前事务可见,其他事务删除会导致当前事务的多次查询结果集不同)
- InnoDB有一个后台进程(称为“purge”进程)会定期扫描并删除这些已标记为删除的记录(且这些记录没有被任何事务引用,类似于JVM的垃圾回收),从而回收磁盘空间。这个过程是异步的,不会影响正常的事务处理。当这条记录被purge进程实际清理了之后,就不能再恢复了
next_record:下一条记录的位置。记录与记录之间是通过链表组织的。
next_record指向下一条记录在聚簇索引中的逻辑位置。帮助InnoDB在读取数据时能够按照聚簇索引的顺序遍历记录
record_type:表示当前记录的类型,0 表示普通记录,1 表示 B+树非叶子节点记录,2 表示最小记录,3 表示最大记录
记录的真实数据
记录真实数据部分除了我们定义的字段,还有三个隐藏字段,分别为:row_id、trx_id、roll_pointer,我们来看下这三个字段是什么。

- row_id
- 不是必须
- 没有指定主键和唯一索引时 InnoDB自动添加
- 占用6个字节
- trx_id
- 事务 id,表示这个数据是由哪个事务生成的。
- trx_id 是必需的,占用 6 个字节。
- roll_pointer
- 这条记录上一个版本的指针。
- roll_pointer 是必需的,占用 7 个字节。
varchar(n) 中 n 最大取值为多少?
varchar(n) 字段类型的 n 代表的是最多存储的字符数量,并不是字节大小。 ascii:一个字符一个字节 UTF-8:一个字符三个字节 utf8mb4:一个字符四个字节
MySQL 规定除了 TEXT、BLOBs 这种大对象类型之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过 65535 个字节。
一行记录最大能存储 65535 字节的数据,但是这个是包含「变长字段字节数列表所占用的字节数」和「NULL 值列表所占用的字节数」。所以,我们在算 varchar(n) 中 n 最大值时,需要减去这两个列表所占用的字节数。
可用字符数,变长字段字节数2个字节(一个字节最大255,两个字节65535)
- 如果允许为NULL,剩余65535-2-1=65532(一个 NULL 值列表)
- 否则剩余 65535-2 = 65533
- 如果是ascii字符及,那么就是字符数量
- 如果是UTF-8,需要除上3
- 如果是utf8mb4,需要除上4
- 多个字段,需要减去其他字段的长度
行溢出后,MySQL 是怎么处理的?
MySQL 中磁盘和内存交互的基本单位是页,一个页的大小一般是 16KB,也就是 16384字节,而一个 varchar(n) 类型的列最多可以存储 65532字节,一些大对象如 TEXT、BLOB 可能存储更多的数据,这时一个页可能就存不了一条记录。这个时候就会发生行溢出,多的数据就会存到另外的「溢出页」中。
如果一个数据页存不了一条记录,InnoDB 存储引擎会自动将溢出的数据存放到「溢出页」中。
- Compact 行格式:多余部分放在「溢出页」,当前页存储部分数据和指向溢出页的地址
- Compressed 和 Dynamic 行格式:当前页只存储指向溢出页的地址,所有数据都在溢出页
