前面介绍过B+树索引,也知道通过B+树能快速的定位用户记录。那么我们就一定能快速的获取数据了吗?其实不然,Innodb查询数据分为两步:①定位记录的位置 ②读取记录。这两步B+树只能加快步骤①,步骤②它无能为力。
试想快递员收发信件,今天要收3号楼每一户共计30户的信件。他有两个方案收取信件:方案①按顺序从第1户一直收到第30户户 方案②随机。那么现实情况下哪种方案更省时省力呢?
这跟Innodb读取数据时面临的问题一样,读取1万行记录,顺序着读出来成为顺序IO,随意读成为随机IO。显然顺序io的性能远比随意io强大,所以我们希望通过索引定位到的记录能是顺序保存在文件中的。那么Innodb如何保证数据是顺序存放的呢?
Innodb保存数据的地方成为表空间,对应着真实的数据库文件。表空间又分为系统表空间和独立表空间,系统表空间对应在mysql/data下的ibdata1文件,此文件初始大小为12M,可以自动扩展。独立表空间对应mysql/data下,Innodb以每个数据库为名建立对应的文件夹,数据库中的每张表都生成xx.frm与xx.idb文件。xx.frm保存表的结构定义信息,xx.idb保存用的记录并可以自动扩展。
对于idb文件来说,可没有什么页的概念,它就是文件系统的一部分,只知道bit、Byte、MByte、GByte。那么问题来了,当我们新增数据的时候,文件系统可不会这么好心替你按照数据库、数据表、主键安排空间,它只知道空间已经用了300B,下次新增的数据将占用第301B的位置,至于这是啥数据,跟谁有关系它可不管。换句话说,你现在新增一条数据占用了磁盘第301B的位置,新增下一条数据时占用磁盘的哪个位置就不一定了。就想我跟相亲的对象一起看电影,买了我的票之后,再买她的票时发现已经不是邻座了,这谁受得了哦。
1、Innodb的页
既然受不了,Innodb当然要解决。既然磁盘系统给分配了idb文件,那就能在这个磁盘文件里搞搞事情,Innodb操作的最小单位是页,大小一般16KB,所以你可以理解idb文件被分成了很多页,相当于一个页的大容器。又因为不同的业务就产生了各种页,但通用的部分还是可以读懂的:
File Header包含页面的检验标志,上一页下一页、页号、页的类型等信息,之前咱们介绍过页的结构。
2、区
Innodb直接操作页,但是一个idb文件可以基于文件系统不断增加,1MB空间就是64个页,1GB对应65536页了。持续增长的情况下就会有数不清的页要管理。这依然是个头大的问题,于是有了区extend的概念。一个区相当于64个页,也就是1M空间。又将每256区分为一组进行分组管理。
2.1、大概形式就是一个表空间的可扩展文件可以管理好多组,每组又管理了256个区,每区又管理着64页。
OK,基于以上的设计。Mysql的大牛为区设计了XDES Entry实体以便操作,这个结构对应着磁盘上一个独立的区,通过这个结构你可以知道相邻的区、属于哪个组,以及区的页面的使用情况。
2.2、新建一张表时,里面一条数据都没有,几页可能就完全能存储,难道也要分一个独立的区吗?考虑到更好的使用磁盘,大牛又把区进行了分类:
- 空间区:没表使用的区,新建数据库时,磁盘里都是这样的区
- 有剩余空间的碎片区:当需要一页或多页(小于32页)的空间时,从这种区中申请
- 没有空间可使用的碎片区:碎片区里的空间被使用完了,不能再被申请了
- 属于某个组织的区:专属于某个组织,别人不能用
大致分为四类:空闲区、碎片未满区、碎片已满区、专属区。基于使用逻辑,Mysql会构建三种链表来组织关系,分别为Free链表、Free_FRAG碎片未满链表、Full_FRAG碎片已满链表。专属区只服务于某个组织,其他区则服务于所有组织。
新表建立需要向碎片区申请页来使用,如果碎片区没有剩余空间,就会向空闲区申请一块空间作为新的碎片区进行分配。当同一种组织(比如索引或记录)使用的页大于32张时,就会直接申请一个空闲区作为自己的专属区,将碎片区的数据复制到专属区,剩余的空间用零值填充。
2.3、目前我们已将磁盘空间与逻辑对象关联;为了更快的查询,大牛又将记录与索引存放在不同的区里,于是又设计出INODE Entry结构,成为段,一张新表会建立两个段,成为数据段与索引段。
段的结构一开始也是从碎片区申请页来使用,当页的数量占满32个时就申请整个区来使用。最后段这一结构也就是一些碎片区的页和一些专属区的集合。同样,段越来越大时,也会带来管理上的麻烦。于是INODE Entry结构里保存了属于段的Free链表、NOT_Full链表、Full链表,通过INODE Entry结构能区别段的身份、段里的内存使用情况等。
一张新表对应着两个段(其实每个索引都对应两个段),每个段对应三条属于段的链表,段还要负责维护区的三条链表。一共要维护9条链表。每个链表固定位置还会存在一个链表基结点,基结点会保存当前链表的长度,头节点或尾节点的指针
3、表空间文件对应多组,每组有256个区,每区有64页。在每组的第一个区中,前几个页面是做统计信息的。第一组第一个区的前几个页保存着本组所有区的使用情况、关联关系、统计信息;除此以外还有表空间的一些统计信息,比如隐藏row_id的递增值,事务trx_id的递增值等。其他组的第一个区前几个页保存的信息作用类似,只是不需要保存表空间的信息了。