InnoDB的表空间
表空间可以看做是InnoDB存储引擎逻辑结构的最高层 ,所有的数据都是存放在表空间中。
1. Extent
对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。
每256个区被划分成一组,第一组的前3个页面是固定的(FSP_HDR,IBUF_BITMAP,INODE),每组的前两个页面是固定的(XDES,IBUF_BITMAP)
1.1 为什么需要引入区的概念?
因为B+树的每一层的节点,都是用一个双向链表连起来的,如果以页作为存储单位的话,在B+树上相邻的两个节点,可能在磁盘上相隔非常远,就会造成磁盘的随机I/O,因此我们应该使得相邻位置的节点,物理位置也尽量相连,形成顺序I/O。
所以,所以才引入了区(extent)的概念,一个区就是在物理位置上连续的64个页。在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配。
1.2 区的分类
- FREE 空闲的区
- FREE_FRAG 有剩余空间的碎片区
- FULL_FRAG 没有剩余空间的碎片区
- FSEG 附属于某个段的区
注意:处于FREE、FREE_FRAG以及FULL_FRAG这三种状态的区都是独立的,算是直属于表空间;而处于FSEG状态的区是附属于某个段的。
1.3 XDES Entry
每一个区都对应着一个XDES Entry结构.XDES Entry的组成如下图
- Segment ID(8字节)
该区所属的段的ID - List Node(12字节)
将XDES Entry连成一个链表,存储的是指向上一个XDES和下一个XDES的指针 - State(4字节)
前面说到的几种区的分类,FREE、FREE_FRAG、FULL_FRAG和FSEG - Page State Bitmap(16字节)
这个部分共占用16个字节,也就是128个比特位。我们说一个区默认有64个页,这128个比特位被划分为64个部分,每个部分2个比特位,对应区中的一个页。这两个比特位的第一个位表示对应的页是否是空闲的,第二个比特位还没有用。
1.3.1 XDES Entry链表
当向某个段中插入数据时
- 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
因此,查找FREE_FRAG 有剩余空间的碎片区,申请一些零散的页面将数据插入,直到为FULL_FRAG。否则找FREE空闲的区,插入数据。而为了查找特定状态的区:
-
把状态为FREE的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FREE链表。
-
把状态为FREE_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FREE_FRAG链表。
-
把状态为FULL_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FULL_FRAG链表。
- 当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间
因为一个段中可以有好多个区,有的区是完全空闲的,有的区还有一些页面可以用,有的区已经没有空闲页面可以用了,为了找出段中特定状态的区:
-
FREE链表:同一个段中,所有页面都是空闲的区对应的XDES Entry结构会被加入到这个链表。注意和直属于表空间的FREE链表区别开了,此处的FREE链表是附属于某个段的。
-
NOT_FULL链表:同一个段中,仍有空闲空间的区对应的XDES Entry结构会被加入到这个链表。
-
FULL链表:同一个段中,已经没有空闲空间的区对应的XDES Entry结构会被加入到这个链表。
1.3.2 链表基节点
上面说到的6种链表,都对应着一个LIst Base Node,因此只要记录下链表基节点,就可以搜索整个链表
-
List Length表明该链表一共有多少节点,
-
First Node Page Number和First Node Offset表明该链表的头节点在表空间中的位置。
-
Last Node Page Number和Last Node Offset表明该链表的尾节点在表空间中的位置。
2.segment
段由若干个零散的页面以及一些完整的区组成。
2.1 为什么需要引入段的概念?
在进行范围查询的时候,其实我们是对B+树的叶子节点进行扫描,如果将B+树的叶子节点和非叶子节点都放在一个地方的话,就会影响范围查询的速度。
因此,InnoDB将叶子节点的区的集合分为一个段,非叶子区的集合同样也分为一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
但是这也带来了一个问题,就是无论如何一个索引都要至少要占用两个区,就是2M的空间,即使数据量远小于2M,就会造成浪费,因此引入了碎片区的概念。
2.3 碎片区
在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。所以此后为某个段分配存储空间的策略是这样的:
-
在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
-
当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间。
2.4 INODE Entry
与区的XDES Entry类似,段也存在INODE Entry,组成如下
- Segment ID
就是指这个INODE Entry结构对应的段的编号(ID)。
- NOT_FULL_N_USED
这个字段指的是在NOT_FULL链表中已经使用了多少个页面。
- 3个List Base Node
分别为段的FREE链表、NOT_FULL链表、FULL链表定义的List Base Node,这样我们想查找某个段的某个链表的头节点和尾节点的时候,就可以直接到这个部分找到对应链表的List Base Node。
- Magic Number:
这个值是用来标记这个INODE Entry是否已经被初始化了(初始化的意思就是把各个字段的值都填进去了)。如果这个数字是值的97937874,表明该INODE Entry已经初始化,否则没有被初始化。。
- Fragment Array Entry
我们前边强调过无数次段是一些零散页面和一些完整的区的集合,每个Fragment Array Entry结构都对应着一个零散的页面,这个结构一共4个字节,表示一个零散页面的页号
3.FSP_HDR(第一组前3个页面之一)
这个页面的类型是FSP_HDR,它存储了表空间的一些整体属性以及第一个组内256个区的对应的XDES Entry结构
由图可得,这个页面由五部分组成
3.1 File Header
记录页的一些通用信息
3.2 File Space Header
表空间的一些整体属性信息
- Space ID 4字节 表空间的ID
- Not Used 4字节 这4个字节未被使用,可以忽略
- Size 4字节 当前表空间占有的页面数
- FREE Limit 4字节 尚未被初始化的最小页号,大于或等于这个页号的区对应的XDES Entry结构都没有被加入FREE链表
- Space Flags 4字节 表空间的一些占用存储空间比较小的属性
- FRAG_N_USED 4字节 FREE_FRAG链表中已使用的页面数量
- List Base Node for FREE List 16字节 FREE链表的基节点
- List Base Node for FREE_FRAG List 16字节 FREE_FRAG链表的基节点
- List Base Node for FULL_FRAG List 16字节 FULL_FRAG链表的基节点
- Next Unused Segment ID 8字节 当前表空间中下一个未使用的 Segment ID
- List Base Node for SEG_INODES_FULL List 16字节 SEG_INODES_FULL链表的基节点
- List Base Node for SEG_INODES_FREE List 16字节 SEG_INODES_FREE链表的基节点
3.3 XDES Entry
在这部分保存的就是一整个组(256个区)的XDES Entry,
3.4 Empty Space
用于页结构的填充,没啥实际意义
3.5 File Trailer
校验页是否完整
4.INODE(第一组前3个页面之一)
这个INODE类型的页就是为了存储INODE Entry结构而存在的
结构上与XDES大体相同,但是引入了List Node for INODE Page List
4.1 List Node for INODE Page List
每个INODE Entry结构占用192字节,一个页面里可以存储85个这样的结构。但是一个表空间中可能存在超过85个段,所以可能一个INODE类型的页面不足以存储所有的段对应的INODE Entry结构,所以就需要额外的INODE类型的页面来存储这些结构。于是引入了List Node for INODE Page List:
-
SEG_INODES_FULL链表:该链表中的INODE类型的页面中已经没有空闲空间来存储额外的INODE Entry结构了。
-
SEG_INODES_FREE链表:该链表中的INODE类型的页面中还有空闲空间来存储额外的INODE Entry结构了。
因此插入INODE Entry时
-
先看看SEG_INODES_FREE链表是否为空,如果不为空,直接从该链表中获取一个节点,也就相当于获取到一个仍有空闲空间的INODE类型的页面,然后把该INODE Entry结构放到该页面中。当该页面中无剩余空间时,就把该页放到SEG_INODES_FULL链表中。
-
如果SEG_INODES_FREE链表为空,则需要从表空间的FREE_FRAG链表中申请一个页面,修改该页面的类型为INODE,把该页面放到SEG_INODES_FREE链表中,与此同时把该INODE Entry结构放入该页面。
5. XDES类型(每组前2个页面之一)
与FSP_HDR类型的页面对比,除了少了File Space Header部分之外,也就是除了少了记录表空间整体属性的部分之外,其余的部分是一样一样的。
6. Segment Header
Page Header部分存在
- PAGE_BTR_SEG_LEAF 10字节 B+树叶子段的头部信息,仅在B+树的根页定义
- PAGE_BTR_SEG_TOP 10字节 B+树非叶子段的头部信息,仅在B+树的根页定义
二者的10字节其实对应Segment Header结构
PAGE_BTR_SEG_LEAF记录着叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量,PAGE_BTR_SEG_TOP记录着非叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量。