Mysql工作原理——InnoDB的表空间

独立表空间结构

区(extent)

表空间中的页太多了,为了更好管理这些页面,设计者提出了的概念,对于16KB的页来说,连续64个页就是一个区,也就是说一个区默认占用1MB空间大小。

不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每256个区被划分为一组:

在这里插入图片描述
其中extent 0 ~ extent 255这256个区算是第一个组,extent 256 ~ extent 511这256个区算是第二个组,extent 512 ~ extent 767这256个区算是第三个组(上图中并未画全第三个组全部的区,请自行 脑补),依此类推可以划分更多的组。这些组的头几个页面的类型都是类似的,就像这样:

在这里插入图片描述

  • 第一组最开始的3个页面是固定的:

    • FSP_HDR:这个类型的页面用来登记整个表空间的一些整体属性以及本组所有的区;
    • IBUF_BITMAP:存储本组所有的区的所有页面关于INSERT BUFFER的信息;
    • INODE:存储许多称为INODE的数据结构;
  • 其余各组最开始的2个页面类型是固定的:

    • XDES:用来登记本组256个区的属性,类似于第一组中的FSP_HDR,不过FSP_HDR中还会额外存储一些表空间属性;

为什么会产生区的概念
在数据量较大的时候,B+树双向链表相邻的两个页之间的物理位置可能离得非常远,这样在范围查找时读取就变成了随机IO,随机IO非常慢,所以应该尽可能让链表中相邻的页的物理位置也相邻,这样范围查询时才可以使用所谓顺序IO。

一个区就是在物理位置上连续的64个页。在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,甚至 在表中的数据十分非常特别多的时候,可以一次性分配多个连续的区。虽然可能造成一点点空间的浪费(数据不足填充满整个区),但是从性能角度看,可以消除很多的随机I/O,功大于过嘛!

段(segment)的概念

为什么会产生段的概念

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

段是以区为单位申请存储空间的,一个区默认占用1M存储空间,那是不是每添加一个索引就得申请两个段,至少占用2M空间呢?对于只存储了几条记录的表这就是天大的浪费。为此,设计者们提出了碎片区的概念,在碎片区并不是所有页都是为了存储同一个段的数据而存在的,而是可以用于不同的目的,比如有些页用于段A、有些页用于段B,碎片区直属于表空间,而不属于任何一个段。

为某个段分配存储空间的策略:

  • 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的;
  • 当某个段已经占用了32个碎片区页面后,就会以完整的区来分配存储空间;
区的分类

但现在我们知道了表空间是由若干个区组成的,而这些区大体可以分为4种类型:

  • (FREE)空闲的区:还没用到这个区中的任何页面;
  • (FREE_FRAG)有剩余空间的区:表示碎片区中还有可用的页面;
  • (FULL_FRAG)没有剩余空间的碎片区:表示碎片区中的所有页面都被使用,没有空闲页面;
  • (FSEG)附属于某个段的区:作为该段的基本单位来使用。

处于FREE、FREE_FRAG以及FULL_FRAG这三种状态的区都是独立的,算是直属于表空间;而处于FSEG状态的区是附属于某个段的。

XDES Entry结构

为了方面管理这些区,设计者提出了一个称为XDES Entry的结构,每一个区都对应着一个XDES Entry这个结构对应区的一些属性。

在这里插入图片描述
XDES Entry是一个40个字节的结构,大致分为4部分:

  • Sement ID(8字节)

每一个段都有一个唯一的编号,用ID表示;

  • List Node(12字节)

这部分可以将若干个 XDES Entry结构串联成一个链表:

在这里插入图片描述
如果想定位表空间内某一个位置的话,只需指定页号以及该位置在指定页号内的偏移量即可。

  • Pre Node Page Number和Pre Node Offset的组合就是指向前一个XDES Entry的指针;

  • Next Node Page Number和Next Node Offset的组合就是指向后一个XDES Entry的指针。

  • State (4字节)

表明区的类型,即FREE、FREE_FRAG、FULL_FRAG和FSEG;

  • Page State Bitmap(16字节)

这个部分占用16位,也就是128比特,每一个区默认有64个页,这12比特位被划分为64各部分,每个部分2个比特位,对应区中一个页。其中第一位表示该页是否空闲,第二位目前还没有用。

XDES Entry链表

我们把事情搞这么麻烦的初心仅仅是想提高 向表插入数据的效率又不至于数据量少的表浪费空间。现在我们知道向表中插入数据本质上就是向表中各个索引的叶子节点段、非叶子节点段插入数据,也知道了不同的区有不同的状态,再回到最初的 起点,捋一捋向某个段中插入数据的过程:

  • 当段中的数据较少的时候,首先会查看表空间中是否有状态为FREE_FRAG空闲空间碎片区,如果找到了,那么从该区中取一些零碎的页把数据插进去,否则到表空间下申请一个状态为FREE的区,把该区状态变为FREE_FRAG,然后从该新申请的区中去一些零碎的页把数据插进去,之后不同的段使用零碎页的时候都从该区中取,知道该页没有空闲空间,状态变为FULL_FRAG。

如何知道零散区的状态?

如何知道表空间里的哪些区是FREE的,哪些区的状态是FREE_FRAG的,哪些区是FULL_FRAG的?

此时,XDES Entry中的List Node部分发挥奇效的时候了,可以通过该部分干三件事:

  • 把状态为FREE的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FREE链表。

  • 把状态为FREE_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FREE_FRAG链表。

  • 把状态为FULL_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FULL_FRAG链表。

每当我们想找一个FREE_FRAG状态的区时,就直接把FREE_FRAG链表的头节点拿出来,从这个节点中取一些零碎的页来插入数据,当这个节点对应的区用完时,就修改一下这个节点的State字段 的值,然后从FREE_FRAG链表中移到FULL_FRAG链表中。同理,如果FREE_FRAG链表中一个节点都没有,那么就直接从FREE链表中取一个节点移动到FREE_FRAG链表的状态,并修改该节点的STATE字段 值为FREE_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结构,其中:

  • List Length表明该链表一共有多少节点,
  • First Node Page Number和First Node Offset表明该链表的头节点在表空间中的位置。
  • Last Node Page Number和Last Node Offset表明该链表的尾节点在表空间中的位置。

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

段的结构

段不对应表空间中某一连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。像每个区都有对应的XDES Entry来记录这个区中的属性一样,设计者为每个段都定义了一个INODE Entry结构来记录段中的属性:

在这里插入图片描述

  • Segment ID :就是指这个INODE Entry结构对应的段的编号;
  • NOT_FULL_N_USED:指的是NOT_NULL链表中已经使用了多少个页面,以后分配空间直接根据这个字段定位空闲页面而不用从头遍历。
  • 3个List Base Node:分别为FREE链表、NOT_NULL链表和FULL链表的头节点和尾结点。
  • Magic Number :标记这个INODE Entry是否被初始化;
  • Fragment Array Entry:由于段是一些零散页面和完整的区的集合,每个Fragment Array Entry 结构都对应着一个零散的页面,这个结构一共4个字节,表示一个零散页面得页号。

各类型页面的详细情况

FSP_HDR类型

第一组的第一个页面,也是表空间的第一个页面,页号为0,它存储了表空间的一些整体属性以及第一组内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链表的基节点,这三个链表的基节点在表空间的位置是固定的就是在第一个页面的File Space Header部分。

  • FRAG_N_USED:表示在FREE_FRAG链表中已经使用的页面数量,方便之后在链表中查找空闲页面。

  • FREE Limit:在该字段表示的页号之前的区都被初始化了,之后的区尚未被初始化。

  • Next Unused Segment ID:存储为段分配的ID,保证每个ID唯一;

  • Space Flags:表空间对一些布尔类型的属性或者可以用几个比特位表示的属性都放在这里存储。

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

    • SEG_INODES_FULL:该链表中的INODE Entry结构填充满了,没空闲空间存放额外的INODE Entry;
    • SEG_INODES_FREE:该链表中的INODE类型的页面都已经仍有空闲空间来存放INODE Entry结构;
XDES Entry部分

我们知道一个XDES Entry结构的大小是40个字节,但是一个页面的大小有限,只能存放有限个XDES Entry结构,所以我们才把256个区划分成一组,在每组第一个页中存放256个XDES Entry。因为每个区对应的XDES Entry结构的地址是固定的,所以访问这些结构就很容易。

XDES类型

每一个XDES Entry对应表空间中的一个区,在区数量非常多的时,一个单独的页可能就不够存放足够多的XDES Entry结构,所以把表空间的区分为了若干个组,每组开头一个页面记录着本组内的所有区对应的XDES Entry,由于第一组的第一个页面有些特殊,因为它也是整个表空间的第一个页面,所以除了记录本组中的所有区对应的XDES Entry意以外,还记录着表空间的一些整体属性,这个页面类型就是FSP_HDR类型,整个表空间里只有这一个类型的页面,除此之外,之后每个分组的第一个页面只需记录本组内所有的区对应的XDES Entry结构即可,称为XDES类型。

在这里插入图片描述
与FSP_HDR类型页面相比,除了少了FileSpace Header部分以及记录表空间整体属性部分以外,其余部分一模一样。

IBUF_BITMAP类型

这个类型页面记录了一些有关Change Buffer的东西。

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结构放入该页 面。

Segment Header结构的运用

记录段与INODE Entry结构的对应关系
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
PAGE_BTR_SEG_LEAF记录着叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量,PAGE_BTR_SEG_TOP记录着非叶子节点段对应的INODE Entry结构的地址是 哪个表空间的哪个页面的哪个偏移量。这样子索引和其对应的段的关系就建立起来了。不过需要注意的一点是,因为一个索引只对应两个段,所以只需要在索引的根页面中记录这两个结构即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值