前言
表空间是一个抽象的概念,对于系统表空间来说,对应着文件系统中一个或多个实际文件;对于每个独立表空间来说,对应着文件系统中一个名为表名.ibd 的实际文件。可以把表空间想象成被切分为许多个页的池子,当我们想为某个表插入一条记录的时候,就从池子中捞出一个对应的页来把数据写进去。
一、数据页
InnoDB是以页为单位管理存储空间的。前面学习过的B+数索引的页类型类型为索引页(也叫数据页),聚簇索引(也就是完整的表数据)和二级索引都是以B+树的形式保存到表空间的。页的通用结构为:
- File Header文件头:记录页面的一些通用信息。包括:
名称 | 占用空间(字节) | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4 | 页的校验和(checksum值) |
FIL_PAGE_OFFSET | 4 | 页号 |
FIL_PAGE_PREV | 4 | 上一个页的页号 |
FIL_PAGE_NEXT | 4 | 下一个页的页号 |
FIL_PAGE_LSN | 8 | 页面被最后修改时对应的日志序列位置(英文名是:Log SequenceNumber) |
FIL_PAGE_TYPE | 2 | 该页的类型 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 页属于哪个表空间 |
FIL_PAGE_TYPE
属性表示页的类型,包括:
类型名称 | 16进制 | 描述 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED | 0X0000 | 最新分配,还未使用 |
FIL_PAGE_UNDO_LOG | 0X0002 | undo日志页 |
FIL_PAGE_INODE | 0X0003 | 存储段的信息 |
FIL_PAGE_IBUF_FREE_LIST | 0X0004 | Change Buffer空闲列表 |
FIL_PAGE_IBUF_BITMAP | 0X0005 | Change Buffer的一些属性 |
FIL_PAGE_TYPE_SYS | 0X0006 | 存储一些系统数据 |
FIL_PAGE_TYPE_TRX_SYS | 0X0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR | 0X0008 | 表空间头部信息 |
FIL_PAGE_TYPE_XDES | 0X0009 | 存储区的一些属性 |
FIL_PAGE_TYPE_BLOG | 0X000A | 溢出页 |
FIL_PAGE_INDEX | 0X45BF | 索引页,也称为数据页 |
- File Trailer 文件尾:校验页是否完整,保证从内存到磁盘刷新时内容的一致性。
二、独立表空间结构
InnoDB 支持许多种类型的表空间,这里重点关注独立表空间和系统表空间的结构。由于系统表空间中额外包含了一些关于整个系统的信息,所以先介绍简单一点的独立表空间。
2.1 区的概念
表空间中的页十分的多,为了更好的管理这些页面,引入了区(extent)的概念。对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每256个区被划分成一组。
上图中,左边不同的颜色代表不同的组,蓝色代表第一组,绿色第二组,依次后推。每个组有256个区,每个区由连续的64个页组成,大小为1MB。
右边表示这些组第一个区的头几个页面类型,可以看出:
-
第一个组最开始的3个页面的类型是固定的。每个属性表示含义:
- FSP_HDR 类型:这个类型的页面是用来登记整个表空间的一些整体属性以及本组所有的区的属性。需要注意的一点是,整个表空间只有一个FSP_HDR 类型的页面。
- IBUF_BITMAP 类型:这个类型的页面是存储本组所有的区的所有页面关于INSERT BUFFER 的信息。
- INODE 类型:这个类型的页面存储了许多称为INODE 的数据结构,用来记录段中的属性。
-
其余各组最开始的2个页面的类型是固定的,也就是说extent 256 、extent 512 这些区最开始的2个页面的类型是固定的。分别是:
- XDES 类型:全称是extent descriptor ,用来登记本组256个区的属性。上边介绍的FSP_HDR 类型的页面其实和XDES 类型的页面的作用类似,只不过FSP_HDR 类型的页面还会额外存储一些表空间的属性。
总结:表空间被划分为许多连续的区,每个区默认由64个页组成,每256个区划分为一组,每个组的最开始的几个页面类型是固定的。
2.2 段的概念
前面说到,一个区就是在物理位置上连续的64个页(区里页面的页号也是连续的)。在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,在表中的数据特别多的时候,甚至可以一次性分配多个连续的区。
以B+树索引举例,如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围查询时,需要对B+树叶子节点中的记录进行顺序扫描,这样效率非常低。针对这种情况,叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段( segment ),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
段是以区为单位申请存储空间的,一个区默认占用1M存储空间。分配后的区,即使段的数据填不满区中所有的页面,那余下的页面也不能被其他段使用。这样,一个区被整个分配给某一个段,对于数据量较小的表来说太浪费存储空间。
为了避免这种情况,提出了碎片区(fragment)的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。引入碎片区后,为某段分配存储空间的策略:
- 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
- 当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间。
2.3 区的分类
状态名 | 含义 |
---|---|
FREE |