数据的基本单位是页,而页又属于各种表空间,比如系统表空间和独立表空间等。表空间里是如何组织数据的?
这里主要看下独立表空间的情况。
每64个连续的页组成一个区(extent),页的大小是16kb,一个区大小也就是1mb。每256个区呢又可以划分为一个组,表空间内是以组为单位管理空间的,如下所示:
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
为啥要创建区的概念?因为相邻的页是由指针连接的,并且是排序的,访问一个页时,很可能要访问相邻的页,比如b+树的叶子节点,我们在查找时便会访问相邻的页。所以,相邻的页最好在物理上也是连续的,这样可以减少io成本。所以说,申请页在一定程度上可以理解为申请区,然后将区里连续的页分配出去,保证查询的效率(这下应该知道为什么尽量使用自增主键索引了吧)。
“一定程度”是啥意思?也就是说存在不以区为单位分配空间的场景呗?是的。想一下,对于一个数据量很少的表,只有几行数据,却直接分配一块1mb大小的空间?这有点浪费。所以,innodb分配空间的逻辑其实是这样的:
1.先从一些零散的页里找空闲页分配;
2.如果一个表占用的空间已经超过了32个零散的页,那么后续的空间分配再从表空间的区里按区为单位分配;
啥是零散的页?
每一个表空间里都会有一些区,这些区专门为刚才说的那些小表用的,也就是说这些区的页可能属于a表也可能属于b表。
段的概念
之前说过,为了让页是连续的,相邻的页是组织在一块区上的。但是表里数据有很多类型,这个组织是分了类的。比如,索引节点和叶子节点的页就没必要放一起了。这就引入的段的概念,段是逻辑概念,物理上可能是不连续的。每一个索引就包含了两个段:索引节点段和数据段。每一个段包含了零散的区+整块的区。前面说到的那些零散的页是属于某一个表空间的,而之后的整块的区则是属于某一个段的。不同的索引从不同的段里申请。
区
每一个区里的这64个页,innodb当然需要存储本区里的统计信息了,这样才好管理啊。innodb使用叫做XDES Entry的结构来管理一个区:
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
segment id:区可能是属于某一个段的,这是段号;
stat:本区的状态,共有4种。未被使用的区;有空闲状态的零散区;无空闲状态的零散区;属于某一段的区;
page stat bitmap:16字节,128bit,每一个区里有64个页,这样的话,用2个bit代表每一个页的状态,实际上只用了一个bit,表示这个页是否满了;
list node:使得每一个xdes entry可以组成双向链表,为啥要组成双向链表?之前不是说分配页时,先从表空间里的存零散页的区分配,再从段里的区分配?innodb需要将这些区组成不同的链表,这样才好分配呀。
对于表空间内零散页组成的区,总共有3种链表:
1.FREE
链表:未被使用的区;
2.FREE_FRAG链表:所有仍然有零散页可分配的区的链表;
3.FULL_FRAG链表:没有零散页可分配的区的链表;
对于段内的区,也有类似的三个链表:FREE
链表;BOT_FULL链表;FULL链表;
段
接下来 看一下的结构:
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
我们知道,段是一些零散的页和段内的区组成的逻辑空间。那么这些信息怎么管理?就是通过上面的这个INODE Entry结构管理的,一个entry对应一个段。
蓝色部分的三个list就是之前介绍的三个链表头结点。下面的Fragment Array Entry代表的是每一个零散的页的页号。那么,通过这么一个entry结构,innodb就能知道一个段里所有的页和区了。
特殊的页类型
我们知道页有各种类型,上一篇我们看过index page类型的页,是用来存储索引数据和数据,这里我们看下与表空间有关的其他的页类型。
FSP_HDR
类型
这是表空间里的第一个组的第一个页的类型。其实就是表空间下的第一个页了,所以这个页的功能是存这个表空间的一些信息。
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
File Header和File Trailer是页的通用信息,之前介绍过了。
File Space Header:存表空间信息,具体如下:
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
中间部分的三个list,就是存前面提到的表空间零散页的区的三个链表的头结点的。最后的两个list后面再说。
entry x:是本组内的各个区的XDES Entry,结果之前也介绍过;这里顺便说下为啥一个组只能有265个区?因为每一组内的XDES Entry都是存在组内特定的一个页内的,而一个页只能有16kb空间,所以只能存265个区,上一个图里页说了。
XDES
类型
从第二组开始,每一个组的第一个页。存的是本组内的信息。
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
看了下,其实和FSP_HDR
类型的页结构没啥区别啊。对啊,确实是,只是少了File Space Header,因为这个信息只需要在FSP_HDR
类型的页记录就好。
IBUF_BITMAP
类型:
每一个组的第二个页面。存储change buffer信息(todo)。
INODE
类型:
每一个组的第三个页面,主要用于存储INODE Entry结构。
(本篇图片全部来自掘金小册中《MySQL是怎样运行的》一书)
里面每一个entry都是一个段。
List Node for INODE Page list:这又是啥list???因为段是逻辑概念,所以理论上可能有多个,那么也就会有多个entry了,那么一个页内可能就存不下了,所以INODE
类型的页也是list,这个结构就是list的前后指针。
再回到之前FSP_HDR
类型页的遗留问题,File Space Header里最后的两个链表是啥意思?
SEG_INODES_FULL
链表:满了的INODE
类型的页面链表,存不下entry了。
SEG_INODES_FREE
链表:仍然有空间的INODE
类型的页面链表。
链表有好多啊。。。
最后推荐一下掘金小册中《MySQL是怎样运行的》一书,本篇也算是对其中一些内容的小结。