84-存放页的大池子-InnoDB的表空间
一、页
1.1、页类型
页类型.png1.2、页通用部分
image.png1.3、页的File Header File Header.png
二、区
对于16KB的页来说,连续的64个页就是一个
区
。256个区
算是第一个组
2.1、表空间 表空间.png
2.2、表空间中的页
image.png2.3、碎片(fragment)区
为了考虑以
完整的区
为单位分配给某个段
对于数据量较小的表太浪费存储空间的这种情况,设计InnoDB的大佬们提出了一个碎片(fragment)区
的概念,也就是在一个碎片区
中,并不是所有的页都是为了存储同一个段
的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A
,有些页用于段B
,有些页甚至哪个段都不属于
。碎片区直属于表空间
,并不属于任何一个段
- 为
某个段
分配存储空间
的策略是这样的- 第一步:在刚开始向表中插入数据的时候,
段
是从某个碎片区
以单个页
为单位来分配存储空间的 - 第二步:当某个段已经占用了32个碎片区页之后,就会以完整的区为单位来分配存储空间
- 第一步:在刚开始向表中插入数据的时候,
2.4、区的分类
image.png处于
FREE、FREE_FRAG以及FULL_FRAG
这三种状态的区都是独立的
,算是直属于表空间
;而处于FSEG
状态的区是附属于某个段
的
2.5、XDES Entry的结构
XDES Entry的结构.png为了方便管理这些区,设计InnoDB的大佬设计了一个称为
XDES Entry
的结构(全称就是Extent Descriptor Entry),每一个区
都对应着一个XDES Entry结构
,这个结构记录了对应的区的一些属性
2.6、 XDES Entry链表
2.6.1、当段中数据较少的时候,首先会查看表空间
中是否有状态为FREE_FRAG的区
,也就是找还有空闲空间的碎片区
,如果找到了,那么从该区中取一些零碎的页
把数据插进去;否则到表空间
下申请一个状态为FREE的区
,也就是空闲的区
,把该区的状态变为FREE_FRAG
,然后从该新申请的区中取一些零碎的页把数据插进去。之后不同的段
使用零碎页
的时候都会从该区
中取,直到该区中没有空闲空间
,然后该区的状态就变成了FULL_FRAG
现在的问题是你怎么知道
表空间
里的哪些区
是FREE
的,哪些区
的状态是FREE_FRAG
的,哪些区
是FULL_FRAG
的?要知道表空间的大小是可以不断增大的,当增长到GB级别的时候,区的数量也就上千了,我们总不能每次都遍历这些区对应的XDES Entry结构
吧?这时候就是XDES Entry
中的List Node部分
发挥奇效的时候了,我们可以通过List Node
中的指针,做这么三件事:
- 1、把状态为
FREE的区
对应的XDES Entry
结构通过List Node
来连接成一个链表
,这个链表我们就称之为FREE链表
- 2、把状态为
FREE_FRAG的区
对应的XDES Entry
结构通过List Node
来连接成一个链表
,这个链表我们就称之为FREE_FRAG链表
- 3、把状态为
FULL_FRAG的区
对应的XDES Entry
结构通过List Node
来连接成一个链表
,这个链表我们就称之为FULL_FRAG链表
2.6.2、当段中数据已经占满了32个零散的页后,就直接申请完整的区来插入数据了
我们怎么知道
哪些区
属于哪个段
的呢?再遍历各个XDES Entry
结构?遍历是不可能遍历的,这辈子都不可能遍历的,有链表还遍历个毛线啊。所以我们把状态为FSEG的区
对应的XDES Entry
结构都加入到一个链表
喽?傻呀,不同的段
哪能共用一个区
呢?你想把索引a的叶子节点段和索引b的叶子节点段都存储到一个区中么?显然我们想要每个段
都有它独立的链表
,所以可以根据段号(也就是Segment ID)来建立链表
,有多少个段就建多少个链表?好像也有点问题,因为一个段
中可以有好多个区
,有的区是完全空闲的,有的区还有一些页可以用,有的区已经没有空闲页可以用了,所以我们有必要继续细分,设计InnoDB的大佬们为每个段中的区对应的XDES Entry
结构建立了三个链表
2.6.3、小结
InnoDB表
,一个聚簇索引,所以共有2个段(叶子段和非叶子段)
,每个段
都会维护上述3个链表
,总共是6个链表
,加上我们上面说过的直属于表空间的3个链表
,整个独立表空间共需要维护9个链表
。所以段在数据量比较大时插入数据的话,会先获取NOT_FULL链表
的头节点,直接把数据插入这个头节点对应的区中即可,如果该区的空间已经被用完,就把该节点移到FULL链表中
-
FREE链表
:同一个段中
,所有页都是空闲的区对应的XDES Entry
结构会被加入到这个链表。注意和直属于表空间的FREE链表区别开了
,此处的FREE链表
是附属于某个段
的 -
NOT_FULL链表
:同一个段中
,仍有空闲空间的区对应的XDES Entry
结构会被加入到这个链表 -
FULL链表
:同一个段中
,已经没有空闲空间的区对应的XDES Entry
结构会被加入到这个链表
2.7、链表基节点
List Base Node结构.png上面光是介绍了一堆链表,可我们怎么找到这些链表呢,或者说怎么找到某个链表的头节点或者尾节点在表空间中的位置呢?设计InnoDB的大佬当然考虑了这个问题,他们设计了一个叫List Base Node的结构,翻译成中文就是链表的基节点。这个结构中包含了链表的头节点和尾节点的指针以及这个链表中包含了多少节点的信息
-
List Length
表明该链表一共有多少节点 -
First Node Page Number
和First Node Offset
表明该链表的头节点
在表空间中的位置 -
Last Node Page Number
和Last Node Offset
表明该链表的尾节点
在表空间中的位置 -
一般我们把某个链表对应的List Base Node结构放置在表空间中固定的位置,这样想找定位某个链表就变得so easy啦
2.8、 链表小结
综上所述,
表空间
是由若干个区
组成的,每个区
都对应一个XDES Entry
的结构,直属于表空间的区
对应的XDES Entry
结构可以分成FREE、FREE_FRAG和FULL_FRAG这3个链表
;
每个段
可以附属若干个区
,每个段中的区
对应的XDES Entry
结构可以分成FREE、NOT_FULL和FULL这3个链表
。每个链表都对应一个List Base Node
的结构,这个结构里记录了链表的头、尾节点的位置以及该链表中包含的节点数
。正是因为这些链表的存在,管理这些区才变成了一件so easy的事情。
四、段
我们提到的范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页放到申请到的区中的话,进行范围扫描的效果就大打折扣了。所以设计InnoDB的大佬们对B+树的叶子节点和非叶子节点进行了区别对待,也就是说
叶子节点
有自己独有的区
,非叶子节点
也有自己独有的区
。存放叶子节点的区的集合就算是一个段(segment)
,存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段
,一个叶子节点段,一个非叶子节点段