文章主要介绍 MySQL 的 InnoDB引擎是怎么存储数据的
InndoDB 逻辑存储结构
我们建一张 tb_user
表,就会生成一个名为 tb_user.ibd
的表空间文件
为了保证顺序IO,表空间被划分为多个连续的数据区,256个连续的数据区称为一个数据区组,一个数据区又由64个连续的数据页组成,数据页包含数据行。
一个数据页大小为 16 KB
一个数据区大小为 64 * 16 KB = 1 MB
一个数据区组大小为 256 * 1 MB = 256 MB
在 InnoDB 中有个参数 innodb_file_per_table
,默认值为 on,表示每张表的数据单独放到一个表空间。
常见的段有数据段,索引段,回滚段。数据即索引,那么数据段即为B+树的叶子节点,索引段即为B+树的非索引节点。
段是一个逻辑概念,段最小申请内存为1MB,为了防止空间浪费,先用32个页大小的碎片页来存放数据,在使用完这些页后才是64个连续页的申请。
页是 InnoDB 磁盘管理的最小单位,大小为16KB,和操作系统的页(4KB)概念不同。
表空间第一个数据区组的第一个数据区前3个数据页是固定的,存放一些描述信息
(1) FSP_HDR:存放表空间和这一组数据区的信息
(2) IBUF_BITMAP:这一组数据页的 insert buffer 数据信息
(3) INODE:存放特殊信息
表空间里的其他各组数据区,每一组数据区的第一个数据区的头两个数据页,都是存放特殊信息
(1) XDES:记录这一组数据区的信息
(2) IBUF_BITMAP:这一组数据页的 insert buffer 数据信息
InnoDB 数据页结构
File Header:文件头,记录页的一些头信息
Page Header:数据页头,记录数据页的状态信息
Supremum和Infimum:最大记录最小记录,限定数据记录的边界
User Records:数据行,实际存储行记录的内容
Free Space:空闲区域,数据页中剩余可插入数据的空间
Page Directory:数据页目录,存放数据记录的相对位置,有时候这些记录指针称为槽(Slots),我们通过B+树索引并不能找到具体的一条记录,只能找到记录所在的数据页,然后通过数据页目录进行二叉查找找到最终的数据
FileTrailer:文件尾部,校验页是否完整的写入磁盘
InnoDB 行记录格式
这里只介绍 compact 行记录格式
变长字段长度列表:字段类型为 varchar 的字段长度,按照列顺序逆序排列
null值列表:记录字段是否为null,0:非null,1:null,占用1个字节
数据头: 40位,固定占用5个字节
隐藏列:
- DB_ROW_ID:如果 Innodb 没有指定主键,会生成一个rowid列,6字节
- DB_TRX_ID:事务ID,6字节
- DB_ROLL_PTR:回滚指针,7字节
在InnoDB中,每张表都有一个主键,如果再创建表时没有显示的定义主键,则InnoDB会按如下方式选择或创建主键:
- 首先判断是否有非空的唯一索引,如果有,则该列即为主键
- 如果不符合上述条件,InnoDB自动创建一个6字节大小的指针
当有多个非空唯一索引时,InnoDB 存储引擎将选择建表时第一个定义的非空唯一索引为主键
行溢出
一个数据页大小为16KB,如果有一个表里有这样一个字段 varchar(65532) ,表示可以存放65532个字符,也就是65532个字节,远大于16KB,说明一个数据页放不下这一行数据。就需要将数据拆分到多个数据页中存放。每行数据都有个指针指向其他数据页中的溢出行。
如图所示,通过多个数据页来存储一行数据,当要读取这一行数据时,就需要加载多个数据页到缓冲池中了。
- Compact格式与Redundant格式,在处理行溢出时,真实数据中只保存 768 字节的前缀,之后的数据都是偏移量,指向溢出页
- Compressed格式与Dynamic格式,在处理行溢出时,数据完全溢出,在数据页只存放20字节的指针,实际的数据都存放在Off Page中
示例
下面通过一个示例说明:
我们创建一张 innodb 存储引擎的表,表结构如下:
create table tb_user (
id varchar(8) not null,
name varchar(10),
gender char(1),
job varchar(12),
address varchar(20)
);
往表中插入几行数据:
insert into tb_user values('1001','melon',null,'chengxuyuan',null);
insert into tb_user values('1002','zhangsan',null,null,'test');
通过 select * from tb_user
能够查询到这样一张视图
数据库数据:
id | name | age | address | job |
---|---|---|---|---|
1001 | melon | NULL | chengxuyuan | NULL |
1002 | zhangsan | NULL | NULL | test |
使用16进制编辑器查看 tb_user.ibd 文件,高亮部分即为行存储的格式,我们看到2行数据是连续存储的,第二行数据紧挨着第一行数据
第一行数据高亮如下
0b 05 04 :变长字段长度,逆序
0a :null值列表
00 00 10 00 30:数据头
00 00 00 00 29 73:RowId
00 00 00 00 3d a5:事务ID
c8 00 00 01 7b 01 10:回滚指针
31 30 30 31 1001
6d 65 6c 6f 6e melon
63 68 65 6e 67 78 75 79 75 61 6e chengxuyuan
1001字符串长度:4,用16进制表示:0x04
melon字符串长度:5,用16进制表示:0x05
chengxuyuan字符串长度:11,用16进制表示:0x0b
倒序展示为: 0b 05 04
允许为null值的有4个字段,2个null,2个非null,null用1表示
null值列表按顺序表示为:0101,倒序排列为:1010,
16进制表示为 0a
第二行数据高亮如下.
04 08 04 :变长字段长度,逆序
06 :null值列表
00 00 18 ff bf :数据头
00 00 00 00 29 74 :RowId
00 00 00 00 3d a6 :事务ID
c9 00 00 01 3f 01 10 :回滚指针
31 30 30 32 1002
7a 68 61 6e 67 73 61 6e zhangsan
74 65 73 74 test
1002字符串长度:4,用16进制表示:0x04
zhangsan 字符串长度:8,用16进制表示:0x08
test字符串长度:4,用16进制表示:0x04
倒序展示为: 04 08 04
允许为null值的有4个字段,2个null,2个非null,null用1表示
null值列表按顺序表示为:0110,倒序排列为:0110,
16进制表示为: 06
参考资料
《MySQL技术内幕 InnoDB存储引擎 第2版》