对应《从根儿上理解MySQL》的第八、九章。
MySQL的数据目录
数据目录是用来存储MySQL在运行过程中产生的数据。
在使用上数据目录 对应着一个系统变量 datadir ,我们在使 用客户端与服务器建立连接之后查看这个系统变量的值,就能找到我们的MySQL中数据目录的地址了。
每当我们创建一张数据库也就是使用CREATE DATABASE时就会创建一个同名的子目录/文件夹,在该文件夹下会有一个db.opt的属性,这个属性存储了数据库相关的各种属性(字符集、比较规则之类的)。
接下来是表的存储,表的存储分成了两种:表结构和表数据。
表结构是以:表名.frm 的文件形式存储在数据库子目录下。
在InnoDB中,表数据是用页来存储的,但为了更好地管理这些数据页,又提出了表空间这一抽象概念。它可以对应文件系统上一个或多个真实文件。
表空间又分为系统表空间和独立表空间。
系统表空间可以对应文件系统中的一个或者多个实际文件。默认在数据目录下创建一个大小可变的文件:ibdata1来存储数据。
独立表空间:MySQL5.6.6以及之后的版本中, InnoDB 并不会默认的把各个表的数据存储到系统表空间中,而是为每一个表 建立一个独立表空间,也就是说我们创建了多少个表,就有多少个独立表空间。文件的名:表名.ibd。
InnoDB的表空间
定义介绍
从上面我们知道,表空间是InnoDB引擎为了存储页数据而设置的抽象概念,在表空间中,还有其他的定义的概念和内容一起来管理这些页。
这些概念由大到小分别是段/组、区、页。
页就是数据页,页的结构上有前后页的指针,但在实际使用中,如果数据量过大,那么页和页在逻辑上是连续的但往往并不是物理连续的,那么我们查找下一页的数据的时候就不是顺序IO而是随机IO了,这会影响我们的查询效率。所以有了区,64个数据页放在一个区中,这样就能实现将随机IO变成顺序IO。一页的大小是16kb,一个区的大小是1M。
段:数据页有多种不同的作用,最简单的就是存储索引的页和存储数据的页,为了将这几种分开在逻辑上设计了段的概念,段是由区组成。段一般分为数据段、索引段和回滚段等。
组:在实际的存储中,区不是按照段来分的,而是256个区组成一个区组,按照区组来划分。
独立结构说明
区
在实际使用中如果我们使用一个表只记录很少的数据,但就为此创建一个区但实际情况并用不上怎么多的其他页来存储数据,那么就会造成空间的浪费。
所以为了解决这种情况就提出了碎片区(直属于表空间)的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页 可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。
分配的策略是这样的:插入数据时是以页为单位插入到碎片区中,当某段的页的数量到达了32页后才会为该段单独创建一个完整的区来存储数据。
基于上面的情况,区的分类分成了以下四种:空闲区,有剩余空间的碎片区,没有剩余空间的碎片区,属于某段的区。
为了便于管理区,设计出了XDES Entry结构来记录区的一些信息。
从上往下介绍一下各个属性:
- Segment ID 段的id号,表示这个区属于哪个段,如果没有分配段就没有意义
- List Node 列表节点,用作指向前后节点的指针。Node Page Number 和 Node Offset 的组合就是指向其他 XDES Entry 的指针。
- 记录该区的状态,即是那四种分类的哪一种。
- Page State Bitmap,记录了该区下64个页的状态,两个比特位记录一个页。
得益于第二个结构的设置,XDES Entry之间可以以链表的形式串联起来,但如果只是将所有的entry串联起来再实际使用中还是异常麻烦,所以实际上会根据entry不同打打状态来分配给不同的链表,所以链表就分成了空闲链表,碎片区链表,满碎片区链表。在使用时只要取出链表头就可以使用了。
除了上述的三种链表,属于段XDES Entry也会形成链表,但这样又遇到上面相似的情况了:一个段可能会有多个区,有的区用完了,有的区还在使用,还有的区尚未使用。所以基于上面的情况属于段的链表也产生了三种上述情况的链表。
除此以外还有List Base Node(链表基节点)这个结构作为链表的开头。在结构上基节点记录了这个链表的头节点、尾节点和节点个数。
段
段是逻辑结构,在实际的物理上不存在一片连续的区域与段对应。
但段还是有对应的结构体来记录信息:INODE Entry。
依然是从上往下介绍:
- segment ID,段ID,上面介绍过
- NOT FULL N USED ,记录在NOT FULL链表中已经使用了多少页面,下次使用NOT FULL链表分配空闲页时可以直接定位到。
- 三种链表的基节点
- Magic Number 记录这个INODE Entry是否初始化。
- 记录零散页面也就是存在碎片区的页面的页号,当满了32个后就会申请单独的区来存储数据。
实际存储情况
在实际的存储中,每256个连续的区被称为一组。每组第一个页面都会记录一些信息,下面就来介绍一下:
FSP_HDR
FSP_HDR:
FSP_HDR结构是第一组的第一个页的结构。大体可以分为5部分
- FileHeader,文件头,和之前的文件头一样,File Tailer也是。
- File Space Header,存储表空间的一些属性,其中就记录了表空间的有的那几种链表的基节点。
- 之前提到的XDES Entry就是在这里存储的,因为一个页的大小有限,所以最后确定一段有256个区。并且这些entry的地址都是固定的,所以在访问上很便捷。
- Empty Space 空闲空间。
其余组的第一个页也是这样用来记录组的信息的,不同的是在File Space Header这个位置变成空的了。
IBUF_BITMAP
每个分组的第二个页面的类型都是 IBUF_BITMAP ,这种类型的页里边记录了一些有 关 Change Buffer 的
INODE
这是第三个特殊页,用来存储INODE Entry 结构。
三个结构已经解释过了就不多写了,下面写一下两个没见过的
- List Node for INODE Page List , 存储上一个INODE页面和下一个INODE页面的指针
- INODE Entry,前面已经知道一个INODE Entry大小是192字节,所以在一页中最多就能存85个INODE Entry。
第一个属性List Node for INODE Page List存储了指针,因为一个表空间可能会超过85个段,所以需要额外的页面来记录段信息。根据之前的链表创建分类方法这里的链表也分成了两类:页面中有空间存储额外的INODE Entry信息和没有空间存储额外INODE Entry信息的链表,SEG_INODES_FREE和SEG_INODES_FULL,两者的基节点就在FSP_HDR的File Space Header中。
在页结构中Page Header有一部分字节记录了INODE Entry的位置,通过这个我们就能将段和段结构(INODE Entry)关联起来。
系统表空间
系统表空间在结构上和独立表空间大同小异,主要是在表空间的开头有许多记录系统属性的页面在。