InnoDB 表空间1---独立表空间

表空间是一个抽象的概念,对于系统表空间来说,对应着文件系统中一个或多个实际文件;对于每个独立表空间来说,对应着文件系统中一个名为表名.ibd的实际文件。大家可以把表空间想象成被切分为许许多多个页的池子,当我们想为某个表插入一条记录的时候,就从池子中捞出一个对应的页来把数据写进去。

回顾旧知识

页类型

InnoDB是以页为单位管理存储空间的,我们的聚簇索引(也就是完整的表数据)和其他的二级索引都是以B+树的形式保存到表空间的,而B+树的节点就是数据页。我们前面说过,这个数据页的类型名其实是:FIL_PAGE_INDEX,除了这种存放索引数据的页类型之外,InnoDB也为了不同的目的设计了若干种不同类型的页,为了唤醒大家的记忆,我们再一次把各种常用的页类型提出来:
在这里插入图片描述

页通用部分

我们前面说过数据页,也就是INDEX类型的页由7个部分组成,其中的两个部分是所有类型的页都通用的。当然我不能寄希望于你把我说的话都记住,所以在这里重新强调一遍,任何类型的页都有下面这种通用的结构:

在这里插入图片描述从上图中可以看出,任何类型的页都会包含这两个部分:

  • File Header:记录页的一些通用信息

  • File Trailer:校验页是否完整,保证从内存到磁盘刷新时内容的一致性。

对于File Trailer我们不再做过多强调,全部忘记了的话可以到将数据页的那一章回顾一下。我们这里再强调一遍File Header的各个组成部分:
在这里插入图片描述

  • 表空间中的每一个页都对应着一个页号,也就是FIL_PAGE_OFFSET,这个页号由4个字节组成,也就是32个比特位,所以一个表空间最多可以拥有2³²个页,如果按照页的默认大小16KB来算,一个表空间最多支持64TB的数据。表空间的第一个页的页号为0,之后的页号分别是1,2,3…依此类推

  • 某些类型的页可以组成链表,链表中的页可以不按照物理顺序存储,而是根据FIL_PAGE_PREV和FIL_PAGE_NEXT来存储上一个页和下一个页的页号。需要注意的是,这两个字段主要是为了INDEX类型的页,也就是我们之前一直说的数据页建立B+树后,为每层节点建立双向链表用的,一般类型的页是不使用这两个字段的。

  • 每个页的类型由FIL_PAGE_TYPE表示,比如像数据页的该字段的值就是0x45BF,我们后边会介绍各种不同类型的页,不同类型的页在该字段上的值是不同的。

独立表空间结构

我们知道InnoDB支持许多种类型的表空间,本章重点关注独立表空间和系统表空间的结构。它们的结构比较相似,但是由于系统表空间中额外包含了一些关于整个系统的信息,所以我们先挑简单一点的独立表空间来介绍,稍后再说系统表空间的结构。

区(extent)的概念

表空间中的页实在是太多了,为了更好的管理这些页,设计InnoDB的大佬们提出了区(英文名:extent)的概念。对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每256个区被划分成一组。画个图表示就是这样:
在这里插入图片描述从上图中我们能得到如下信息:

  1. 第一个组最开始的3个页的类型是固定的,也就是说extent 0这个区最开始的3个页的类型是固定的,分别是:
  • FSP_HDR类型:这个类型的页是用来登记整个表空间的一些整体属性以及本组所有的区,也就是extent 0 ~ extent 255这256个区的属性,稍后详细介绍。需要注意的一点是,整个表空间只有一个FSP_HDR类型的页。

  • IBUF_BITMAP类型:这个类型的页是存储本组所有的区的所有页关于INSERT BUFFER的信息。当然,你现在不用知道什么是个INSERT BUFFER,后边会详细说到你吐。

  • INODE类型:这个类型的页存储了许多称为INODE的数据结构,还是那句话,现在你不需要知道什么是个INODE,后边儿会说到你吐。

  1. 其余各组最开始的2个页的类型是固定的,也就是说extent 256、extent 512这些区最开始的2个页的类型是固定的,分别是:
  • XDES类型:全称是extent descriptor,用来登记本组256个区的属性,也就是说对于在extent 256区中的该类型页存储的就是extent 256 ~ extent 511这些区的属性,对于在extent 512区中的该类型页存储的就是extent 512 ~ extent 767这些区的属性。上面介绍的FSP_HDR类型的页其实和XDES类型的页的作用类似,只不过FSP_HDR类型的页还会额外存储一些表空间的属性。

  • IBUF_BITMAP类型:上面介绍过了。

段(segment)的概念

事情到这里就结束了么?太天真了,我们提到的范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页放到申请到的区中的话,进行范围扫描的效果就大打折扣了。所以设计InnoDB的大佬们对B+树的叶子节点和非叶子节点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。

所以现在段不能仅定义为是某些区的集合,更精确的应该是某些零散的页以及一些完整的区的集合。除了索引的叶子节点段和非叶子节点段之外,InnoDB中还有为存储一些特殊的数据而定义的段,比如回滚段,当然我们现在并不关心别的类型的段,现在只需要知道段是一些零散的页以及一些完整的区的集合就好了。

区的分类----也可以称为区的状态

FREE:没有用到这个区的任何一个页面。
FREE_FRAG:有剩余空闲页的碎片区。
FULL_FRAG:没有剩余空闲页的碎片区。
FSEG:专属某一个段的区。

为了方便管理这些区,设计InnoDB的大佬设计了一个称为XDES Entry的结构(全称就是Extent Descriptor Entry),每一个区都对应着一个XDES Entry结构,这个结构记录了对应的区的一些属性。我们先看图来对这个结构有个大致的了解:
在这里插入图片描述

  • State(4字节)
    这个字段表明区的状态。可选的值就是我们前面说过的那4个,分别是:FREE、FREE_FRAG、FULL_FRAG和FSEG。具体释义就不多介绍了,前面说的够仔细了。
  • Page State Bitmap(16字节)
    这个部分共占用16个字节,也就是128个比特位。我们说一个区默认有64个页,这128个比特位被划分为64个部分,每个部分2个比特位,对应区中的一个页。比如Page State Bitmap部分的第1和第2个比特位对应着区中的第1个页,第3和第4个比特位对应着区中的第2个页,依此类推,Page State Bitmap部分的第127和128个比特位对应着区中的第64个页。这两个比特位的第一个位表示对应的页是否是空闲的,第二个比特位还没有用。

XDES Entry链表

到现在为止,我们已经提出了五花八门的概念,什么区、段、碎片区、附属于段的区、XDES Entry结构等等的概念,走远了千万别忘了自己为什么出发,我们把事情搞这么麻烦的初心,仅仅是想提高向表插入数据的效率(减少随机IO 引入extent的概念),又不至于数据量少的表浪费空间。现在我们知道向表中插入数据本质上就是向表中各个索引的叶子节点段、非叶子节点段插入数据,也知道了不同的区有不同的状态,再回到最初的起点,捋一捋向某个段中插入数据的过程

  • 当段中数据较少的时候,首先会查看表空间中是否有状态为FREE_FRAG的区,也就是找还有空闲空间的碎片区,如果找到了,那么从该区中取一些零碎的页把数据插进去;否则到表空间下申请一个状态为FREE的区,也就是空闲的区,把该区的状态变为FREE_FRAG,然后从该新申请的区中取一些零碎的页把数据插进去。之后不同的段使用零碎页的时候都会从该区中取,直到该区中没有空闲空间,然后该区的状态就变成了FULL_FRAG。
  • 当段中数据已经占满了32个零散的页后,就直接申请完整的区来插入数据了。设计InnoDB的大佬们为每个段中的区对应的XDES Entry结构建立了三个链表:
    • FREE链表:同一个段中,所有页都是空闲的区对应的XDES Entry结构会被加入到这个链表。注意和直属于表空间的FREE链表区别开了,此处的FREE链表是附属于某个段的。

    • NOT_FULL链表:同一个段中,仍有空闲空间的区对应的XDES Entry结构会被加入到这个链表。

    • FULL链表:同一个段中,已经没有空闲空间的区对应的XDES Entry结构会被加入到这个链表。

再次强调一遍,每一个索引都对应两个段,每个段都会维护上述的3个链表,比如下面这个表:

CREATE TABLE t (
  c1 INT NOT NULL AUTO_INCREMENT,
  c2 VARCHAR(100),
  c3 VARCHAR(100),
  PRIMARY KEY (c1),
  KEY idx_c2 (c2)
)ENGINE=InnoDB;

这个表t共有两个索引,一个聚簇索引,一个二级索引idx_c2,所以这个表共有4个段,每个段都会维护上述3个链表,总共是12个链表,加上我们上面说过的直属于表空间的3个链表,整个独立表空间共需要维护15个链表。所以段在数据量比较大时插入数据的话,会先获取NOT_FULL链表的头节点,直接把数据插入这个头节点对应的区中即可,如果该区的空间已经被用完,就把该节点移到FULL链表中。

链表基结点

上面光是介绍了一堆链表,可我们怎么找到这些链表呢,或者说怎么找到某个链表的头节点或者尾节点在表空间中的位置呢? 设计InnoDB的大佬当然考虑了这个问题,他们设计了一个叫List Base Node的结构,翻译成中文就是链表的基节点。这个结构中包含了链表的头节点和尾节点的指针以及这个链表中包含了多少节点的信息,我们画图看一下这个结构的示意图:
在这里插入图片描述一般我们把某个链表对应的 List Base Node 结构放置在表空间中固定的位置(直属于表空间的FREE链表的基节点、FREE_FRAG链表的基节点、FULL_FRAG链表的基节点的 3个 base node 在 FSP_HDR 类型的页面中 File Space Header中,参考下文 FSP_HDR 类型;属于段的 FREE链表、NOT_FULL链表、FULL链表 3个 base node 在 INODE entry中,参考下文段的结构),这样想找定位某个链表就变得so easy啦。

链表小结

综上所述,表空间是由若干个区组成的,每个区都对应一个XDES Entry的结构,直属于表空间的区对应的XDES Entry结构可以分成FREE、FREE_FRAG和FULL_FRAG这3个链表;每个段可以附属若干个区,每个段中的区对应的XDES Entry结构可以分成FREE、NOT_FULL和FULL这3个链表。每个链表都对应一个List Base Node的结构,这个结构里记录了链表的头、尾节点的位置以及该链表中包含的节点数。正是因为这些链表的存在,管理这些区才变成了一件so easy的事情。

段的结构

我们前面说过,段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页以及一些完整的区组成。像每个区都有对应的XDES Entry来记录这个区中的属性一样,设计InnoDB的大佬为每个段都定义了一个 INODE Entry 结构来记录一下段中的属性。
在这里插入图片描述

  • 3个List Base Node
    分别为段的FREE链表、NOT_FULL链表、FULL链表定义了List Base Node,这样我们想查找某个段的某个链表的头节点和尾节点的时候,就可以直接到这个部分找到对应链表的List Base Node。

各类型页面详细情况

到现在为止我们已经大概清楚了表空间、段、区、XDES Entry、INODE Entry、各种以XDES Enty为节点的链表的基本概念了,可是总有一种飞在天上不踏实的感觉,每个区对应的XDES Entry结构到底存储在表空间的什么地方?直属于表空间的FREE、FREE_FRAG、FULL_FRAG链表的基节点到底存储在表空间的什么地方?>每个段对应的INODE Entry结构到底存在表空间的什么地方?我们前面介绍了每256个连续的区算是一个组,想解决刚才提出来的这些个疑问还得从每个组开头的一些类型相同的页说起,接下来我们一个页一个页的分析,真相马上就要浮出水面了。

FSP_HDR 类型----表空间的第一页非常关键,存放 XDES Entry 结构的页

首先看第一个组的第一个页,当然也是表空间的第一个页,页号为0。这个页的类型是FSP_HDR,它存储了表空间的一些整体属性以及第一个组内256个区的对应的XDES Entry结构,直接看这个类型的页的示意图:
在这里插入图片描述File Space Header 部分:---- 存放链表基节点
在这里插入图片描述

  • List Base Node for FREE List、List Base Node for FREE_FRAG List、List Base Node for FULL_FRAG List。
    这三个大家看着太亲切了,分别是直属于表空间的FREE链表的基节点、FREE_FRAG链表的基节点、FULL_FRAG链表的基节点,这三个链表的基节点在表空间的位置是固定的,就是在表空间的第一个页(也就是FSP_HDR类型的页)的File Space Header部分(充分解释了链表基节点的内容)。所以之后定位这几个链表就so easy啦。

  • List Base Node for SEG_INODES_FULL List 和 List Base Node for SEG_INODES_FREE List
    每个段对应的INODE Entry结构会集中存放到一个类型位INODE的页中,如果表空间中的段特别多,则会有多个INODE Entry结构,可能一个页放不下,这些INODE类型的页会组成两种列表:

    • SEG_INODES_FULL链表,该链表中的INODE类型的页都已经被INODE Entry结构填充满了,没空闲空间存放额外的INODE Entry了。

    • SEG_INODES_FREE链表,该链表中的INODE类型的页都已经仍有空闲空间来存放INODE Entry结构。

XDES 类型----基本和 FSP_HEADER 类型类似,存放 XDES Entry 结构的页

我们说过,每一个 XDES Entry 结构对应表空间的一个区,虽然一个XDES Entry结构只占用40字节,但你抵不住表空间的区的数量也多啊。在区的数量非常多时,一个单独的页可能就不够存放足够多的XDES Entry结构,所以我们把表空间的区分为了若干个组,每组开头的一个页记录着本组内所有的区对应的XDES Entry结构。由于第一个组的第一个页有些特殊,因为它也是整个表空间的第一个页,所以除了记录本组中的所有区对应的XDES Entry结构以外,还记录着表空间的一些整体属性,这个页的类型就是我们刚刚说完的FSP_HDR类型,整个表空间里只有一个这个类型的页。除去第一个分组以外,之后的每个分组的第一个页只需要记录本组内所有的区对应的XDES Entry结构即可,不需要再记录表空间的属性了,为了和FSP_HDR类型做区别,我们把之后每个分组的第一个页的类型定义为XDES,它的结构和FSP_HDR类型是非常相似的:
在这里插入图片描述

IBUF_BTMAP 类型

暂时忽略

INODE 类型----存放 INODE Entry 的的页

再次对比前面介绍表空间的图,第一个分组的第三个页的类型是INODE。我们前面说过设计InnoDB的大佬为每个索引定义了两个段,而且为某些特殊功能定义了些特殊的段。为了方便管理,他们又为每个段设计了一个INODE Entry结构,这个结构中记录了关于这个段的相关属性。而我们这会儿要介绍的这个INODE类型的页就是为了存储INODE Entry结构而存在的。
在这里插入图片描述重点看一下List Node for INODE Page List这个玩意儿,因为一个表空间中可能存在超过85个段,所以可能一个INODE类型的页不足以存储所有的段对应的INODE Entry结构,所以就需要额外的INODE类型的页来存储这些结构。还是为了方便管理这些INODE类型的页,设计InnoDB的大佬们将这些INODE类型的页串联成两个不同的链表

  • SEG_INODES_FULL链表:该链表中的INODE类型的页中已经没有空闲空间来存储额外的INODE Entry结构了。

  • SEG_INODES_FREE链表:该链表中的INODE类型的页中还有空闲空间来存储额外的INODE Entry结构了

每当我们新创建一个段(创建索引时就会创建段)时,都会创建一个INODE Entry结构与之对应,存储INODE Entry的大致过程就是这样的:

  • 先看看 SEG_INODES_FREE 链表是否为空,如果不为空,直接从该链表中获取一个节点,也就相当于获取到一个仍有空闲空间的INODE类型的页,然后把该INODE Entry结构防到该页中。当该页中无剩余空间时,就把该页放到SEG_INODES_FULL链表中。

  • 如果 SEG_INODES_FREE 链表为空,则需要从表空间的FREE_FRAG链表中申请一个页,修改该页的类型为INODE,把该页放到SEG_INODES_FREE链表中,与此同时把该INODE Entry结构放入该页。

Segment Header 结构的运用----索引页的根页中的存放叶子和非叶子节点段的INODE Entry

我们知道一个索引会产生两个段,分别是叶子节点段和非叶子节点段,而每个段都会对应一个INODE Entry结构,那我们怎么知道某个段对应哪个INODE Entry结构呢?所以得找个地方记下来这个对应关系。希望你还记得我们在介绍数据页,也就是INDEX类型的页时有一个Page Header部分:
在这里插入图片描述这样子就很清晰了,PAGE_BTR_SEG_LEAF记录着叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页的哪个偏移量,PAGE_BTR_SEG_TOP记录着非叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页的哪个偏移量。这样子索引和其对应的段的关系就建立起来了。不过需要注意的一点是,因为一个索引只对应两个段,所以只需要在索引的根页中记录这两个结构即可。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值