记录的存储结构
页作为磁盘和内存之间的交互单位,页的大小一般为16KB。在一般情况下,一次最少从磁盘中读取16KB的内容到内存,一次最少把内存的16KB刷新到磁盘
InnoDB行格式
InnoDB行格式有4种,分别是COMPACT、REDUNDANT、DYNAMIC、COMPRESSED。其中,DYNAMIC行格式是MySQL 5.7 / 5.8版本默认的
COMPACT行格式
记录的额外信息
- 变成字段列表:记录字段真实数据所占用的字节数。变长字段,如TEXT、VARCHAR等
- NULL值列表:每个允许存NULL的列对应一个二进制位
- 记录头信息:
| 名称 | 大小 | 描述 |
|---|---|---|
| deleted_flag | 1 | 标记该记录是否被删除 |
| min_rec_flag | 1 | B+树的每层非叶子节点中最小目录项记录 |
| n_owned | 4 | 当前记录拥有的记录数(分组后带头大哥所代表记录总数,小弟为0) |
| heap_no | 13 | 当前记录在页面堆中的位置(记录紧密排列的结构称为堆) |
| record_type | 3 | 当前记录类型:0表示普通记录 1表示B+树非叶子节点目录项记录 2表示Infimum记录 3表示Supremum记录 |
| next_record | 16 | 下一条记录的相对位置 |
被删除的记录还在页中吗?为什么不直接删除?
被删除的记录还在页中,还在真实的磁盘上,只是将记录的行格式上的头信息的删除标记位deleted_flag设置为1。如果直接移除它们,需要在磁盘上重新排列其他记录,带来性能损耗。被删除的记录会组成一个垃圾链表,垃圾链表占用的空间是可以重用的,如果新的记录插入到表中,可以直接覆盖垃圾链表占用的空间。
堆中记录的heap_no值在分配后就不会发生改变。
Infimum记录的下一条记录就是本页中主键值最小的用户记录,本页中主键值最大的用户记录的下一条记录就是Supremum记录。规定Infimum记录为页面中最小记录,Supremum记录为页面中最大的记录
记录在页中的示意图
记录的真实数据
InnoDB存储引擎会为每条记录都添加 transaction_id 和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)
| 名称 | 大小 | 占用空间 | 描述 |
|---|---|---|---|
row_id | 否 | 6字节 | 行ID,唯一标识一条记录 |
transaction_id | 是 | 6字节 | 事务ID |
roll_pointer | 是 | 7字节 | 回滚指针 |
溢出列(针对DYNAMIC行格式)
一条记录的某个列中存储的数据占用字节数非常多时,会导致溢出列。溢出列的所有数据都存储在溢出页中,行格式的记录的真实数据某个字段只存储指向溢出页的地址
数据页结构
常用的页面类型
- 存放表中记录的页,称为数据页/索引页
- 存放表空间头部信息的页
- 存放Change Buffer的页
- 存放INODE信息的页
- 存放undo日志信息的页
数据页的结构
16KB的数据页可以划分为以下几个部分
| 名称 | 中文名 | 占用空间大小 | 简单描述 |
|---|---|---|---|
| File Header | 文件头部 | 38字节 | 页的一些通用信息 |
| Page Header | 页头部 | 56字节 | 数据页专有的一些信息 |
| Infimum + Supremum | 最小记录和最大记录 | 26字节 | 两个虚拟的行记录 |
| User Records | 用户记录 | 不确定 | 实际存储的行记录内容 |
| Free Space | 空闲空间 | 不确定 | 页中尚未使用的空间 |
| Page Directory | 页目录 | 不确定 | 页中的某些记录的相对位置 |
| File Trailer | 文件尾部 | 8字节 | 校验页是否完整 |
1. 记录在数据页中的存储
表中的记录会按照指定的行格式存储在User Records部分。每当插入一条记录时,都会从Free Space申请一记录大小的空间。如果该数据页的剩余空间使用完,就去申请新的页。记录在页中按主键值从小到大的顺序组成一个单项链表
2. Page Directory(页目录)
规定:Infimum记录所在组只能有1条记录,Supremum记录所在组所有拥有的记录条数只能在1-8之间,剩下的分组中记录的条数只能在4-8之间。
槽存放每组中最大的那条记录在页面中的地址偏移量,通过槽可以快速定位对应记录的主键值
一个数据页中根据主键值查找指定记录时的过程如下:
- 通过二分法确定该记录所在分组对应的槽,找到该槽所在分组中主键值最小的那条记录
- 通过记录的next_record属性遍历该槽所在的组中的各个记录
查找主键值为6的记录过程,自己回忆
3. Page Header页面头部
- 数据页存储多少条记录
- 页目录中槽的数量
4. File Header文件头部
页的第一组成部分
- 页的校验和
- 页号
- 上一个页的页号
- 下一个页的页号
5. File Trailer文件尾部
- 检测一个页是否完整
思考
- char和varchar的区别?
char的长度是固定的,字符不足用空字符填充。varchar的长度是可变的。除此之外,如果字段的字符集是是定长编码的字符集,比如ASCII,那么char类型的字段真实数据长度是不会记录在行格式的【变长字段长度列表中】 - 变长字段占用的存储空间哪两部分
该字段占用的字节数和真正的数据内容 - 什么情况下会导致溢出列?
一条记录的某个列中存储数据占用的字节数非常多的时候,会导致溢出列 - 为什么VARCHAR不能太长?
VARCHAR的字段太长可能会导致溢出列 - 表的定义200个字段会有什么问题?
可能容易发生溢出列。一个页16KB大小,字段越多,一个页存储的记录越少。