文章目录
- 一、概念引入
- 1、页
- 2、区
- 3、段
- 二、页的结构
- 1、File Header
- 2、FIle Trailer
- 三、区的结构
- 1、分类
- 2、XDES Entry
- 3、XDES Entry链表
- 四、段的结构
- 五、独立表空间
- 1、FSP_HDR页
- 2、XDES页
- 3、IBUF_BITMAP页
- 4、INODE页
- 5、INDEX页
- 六、系统表空间
一、概念引入
1、页
InnoDB是以页为单位管理存储空间的,在InnoDB中针对不同的目的设计了各种不同类型的页面。如下(省略了FIL_PAGE或FiL_PAGE_TYPE的前缀):
- ALLOCATED:最新分配,还未使用
- UNDO_LOG:undo日志页
- INODE:用于存储段的信息
- IBUF_FREE_LIST:Change Buffer空闲列表
- IBUF_BITMAP:Change Buffer的一些属性
- SYS:存储一些系统数据
- TRX_SYS:事务系统数据
- FSP_HDR:表空间头部信息
- XDES:用于存储区的一些属性
- BLOB:溢出页
- INDEX:索引页/数据页(我们的聚簇索引和其他的二级索引都是以B+树的形式保存到表空间,而B+树的节点就是索引页/数据页)
2、区
**区是由连续页组成的空间,在任何情况下每个区的大小都为1MB。**无论是系统表空间还是独立表空间,都可以看成是由若干个连续的区组成的。
区是用于向磁盘申请存储空间的单位。之所以引入区的概念,是因为如果以页尾单位来分配存储空间,双向链表(B+树每一层的页都会形成一个双向链表)相邻的两个页的物理位置可能离的非常远。为了尽量让页面链表中相邻的页的物理位置也相邻,使得每次扫描节点中的记录时可以使用顺序IO,所以每次会申请一块更大的存储空间(即区),之后页从区中分配,而不是每次都以单独的页去申请。
3、段
InnoDB中在区之上又引入了段的概念,段其实不对应表空间中某一个连续的物理区域,它是一个逻辑上的概念,由若干个零散的页面和一些完整的区组成。实际上就是对表空间中的页进行分类,将存放相同类型数据的页统筹到一起进行管理,例如undo页便组成了回滚段。此外每个索引也按照B+树的叶子结点和非叶子节点进行区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段,存放非叶子结点的区的集合也是一个段。也就是说一个索引会生成两个段:一个叶子结点段和一个非叶子节点段。
由于一个区默认是占用1MB的存储空间,对于存储记录比较少的表而言,也许根本用不完这么多的空间。为了节省空间,InnoDB引入了碎片区的概念,在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,碎片区中的页可以用于不同的目的,有些属于段A、有些属于段B,有些甚至不属于任何段。碎片区直属于表空间,并不属于任何一个段。
段进行空间分配的策略如下:
- 刚开始向表中插入数据时,段是从某个碎片区以单个页面为单位来分配存储空间的
- 当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间
- 原本占用的碎片区页面不会复制到新申请的完整的区中
这也就是最开始所说的,段由若干个零散的页面和一些完整的区组成。
二、页的结构
InnoDB中的页有两个固定的部分:
- File Header(38 字节):文件头
- File Trailer(8 字节):文件结尾信息
File Header和File Trailer中间的部分根据类型的不同有着不同的结构,而这两个部分是所有页面统一的。
1、File Header
File Header 是用来记录各种页都适用的一些通用信息,由8个部分组成:
- FIL_PAGE_SPACE_OR_CHKSUM:当前页面的校验和
- FIL_PAGE_OFFSET:页号
- FIL_PAGE_PREV:上一个页的页号
- FIL_PAGE_NEXT:下一个页的页号
- FIL_PAGE_LSN:该页最后被修改的日志序列位置
- FIL_PAGE_TYPE:页的类型
- FIL_PAGE_FILE_FLUSH_LSN:仅在系统表空间的一个页中定义,代表文件至少被更新到了该LSN值。对于独立表空间,该值都为0
- FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID:页属于哪个表空间
表空间中的每一个页都对应着一个页号,表空间中第一个页的页号为0,之后的也好分别是1、2、3等。某些类型的页可以通过FIL_PAGE_PREV和FIL_PAGE_NEXT组成链表(主要用于索引页/数据页,B+树的特性决定的),链表中相邻的两个页面的页号可以不连续。
2、FIle Trailer
File Trailer用于校验是否完整,保证页面从内存刷新到磁盘后内容是相同的,由2个部分组成:
- 校验和:与FIle Header中的校验和相对应
- LSN:与File Heder中的LSN相对应
如果页面刷新成功,则页首和页尾的校验和以及LSN应该是一致的。如果刷新了一部分后断电了,那么头部的校验和就代表着已经修改过的页,而尾部的校验和代表着原先的页,两者不同则说面刷新期间发生了错误。
三、区的结构
1、分类
- FREE:空闲的区。现在还没有用到这个区中的任何页面
- FREE_FRAG:有剩余空闲页面的碎片区。表示碎片区中还有可分配的空闲页面
- FULL_FRAG:没有剩余空闲页面的碎片区。表示碎片区中的所有页面都被分配使用,没有空闲页面
- FSEG:附属于某个段的区。区别于碎片区,这些区中的页面完全用于存储该段中的数据
FREE、FREE_FRAG、FULL_FRAG这3种状态的区都是独立的,算是直属于表空间,而处于FSEG状态的区是附属于某个段的。
2、XDES Entry
为了方便管理这些区,InnoDB中有一个称为XDES Entry的结构。每一个区都对应着一个XDES Entry结构,这个结构记录了对应的区的一些属性。
XDES Entry结构有40字节,大致分为4个部分:
-
Segment ID(8字节):每一个段都有一个唯一的编号,标志当前区是分配给哪个段的
-
List Node(12字节):这个部分可以将若干个XDES Entry结构串成一个链表
- Prev Node Page Number和Prev Node Offset:指向前一个XDES Entry的指针
- Next Node Page Number和Next Node Offsett:指向下一个XDES Entry的指针
-
State(4字节):表名区的状态,即前面提到的FREE、FREE_FRAG、FULL_FRAG和FSEG
-
Page State Bitmap(16字节):划分为64个部分,每个部分有2位,对应区中的一个页。其中第一位表示对应的页是否空闲,第二位还没有用到
3、XDES Entry链表
- 当段中数据较少时,首先会查看表空间中是否有状态为FREE_FRAG的区(也就是查找还有空闲页面的碎片区)
- 如果没有则到表空间中申请一个状态为FREE的区,把该区的状态变为FREE_FRAG,然后从中取一个零散页把数据插进去
- 如果段中的数据已经占满了32个零散的页则申请完整的区来插入
由于表空间是可以不断增大的,不可能每次查找区的的时候都去遍历整个表所有区的XDES Entry。所以对于直属于表空间的三种类型的区,使用List Node维护了对应的三种类型(FREE、FREE_FRAG、FULL_FRAG)的链表来方便查找,之后使用时直接从对应的链表的头结点取即可。对于直属于段的区,也维护了三种类型(FREE、NOT_FULL、FULL)的链表。
如果一个表有两个索引,那么将会有4个段。对于每个段,都需要维护三种类型的链表,再加上直属于表空间的3个链表,总共需要维护15个类型的链表。
使用链表可以将不同类型的区给区分出来,那我们首先还需要找到链表的头结点才可以使用。因此前面介绍的**每个链表都会对应有一个List Base Node结构,这个结构中包含了链表的头节点和尾节点的指针和这个链表包含了多少个节点的信息。**List Base Node结构会放置在表空间中的一个固定位置,那么要查找某种类型的区时就直接去这个固定的位置拿到List Base Node然后拿到它的头节点即可。
四、段的结构
像每个区都有一个对应的的XDES Entry结构一样,每个段也都定义了一个INDOE Entry结构来记录段中的信息。INODE Entry的属性如下:
- Segment ID:这个结构对应的段的编号
- NOT_FULL_N_USED:在NOT_FULL链表中已经使用了多少个页面
- 3个List Base Node:前面提到的每个段都会维护有直属于段的三个链表,INODE Entry中则存放了这三个链表的List Base Node结构
- Magic Number:标记段是否已经被初始化
- Fragment Array Entry:前面三个链表中维护的都是完整的区,而段中还会有一些从碎片区申请的零碎的页,它们的页号则存放在这里
五、独立表空间
在MySQL5.6.6及以后的版本中,**InnoDB不再默认把各个表的数据存储到系统表空间,而是为每个表建立一个独立表空间。**也就是创建了多少个表就有多少个独立表空间。在使用独立表空间来存储数据时,会在该表所属数据库对应的子目录下创建一个表示该独立表空间的文件,其文件名和表名相同,文件后缀为.ibd。
前面我们讲到了区和段的概念,也讲到了它们对应的XDES Entry结构和INODE Entry结构以及各种以XDES Entry为节点的链表。我们知道了页的申请需要用到这些结构,那这些结构存放在那呢,这就需要了解整个表空间的结构了。
表空间实际上就是由许许多多的页组成的,为了更好的管理页,我们引入了区的概念,对于16KB的页而言,64个连续的页面就是一个区。而表空间可以看为是由若干个连续的区组成,其中每256个区被划分成了一组(同样是为了更好的管理),每个组中的前几个页面会负责维护这个组的一些信息。
-
第一个组最开始的三个页面的类型是固定的
- FSP_HDR:用来登记整个表空间的一些整体属性以及本组256个区的属性(一个表空间只有一个FSP_HDR页)
- IBUF_BITMAP:存储关于Change Buffer的一些信息
- INODE:存储了整个表中所有段的INODE Entry
-
其余所有组的前两个页面的类型是固定的:
- XDES:登记本组256个区的属性
- IBUF_BITMAP:存储关于Change Buffer的一些信息
1、FSP_HDR页
这是表空间的第一个页面,页号为0,类型为FSP_HDR,其组成如下:
-
FIle Header:页的通用结构,不再赘述
-
File Space Header:表空间头部,用来描述表空间的一些整体属性信息
-
表空间ID
-
表空间拥有的页面数
-
尚未被初始化的最小页号,大于或等于这个页号的区对那个的XDES Entry结构都没有被加入FREE链表(对于表空间而言,可能在初始化或者自增长时分配的磁盘空间很大,而这些磁盘空间的空闲区并没有直接加入到FREE链表,而是等到需要使用时在初始化加入到FREE链表中)
-
FREE_FRAG链表中已使用的页面数量
-
FREE、FREE_FRAG、FULL_FRAG链表的基节点
-
表空间中下一个未使用的段ID(每次创建新段时从这里获取ID,使用后将该值加一即可)
-
SEG_INODES_FULL和SEG_INODES_FREE链表的基节点
- 每个段对应的INODE Entry结构会集中存放到一个类型为INODE的页(前面也提到了,第一个组的第三个页面就是INODE类型的页,其中就会存放表空间的所有INODE Entry)
- 如果表空间的段特别多,那么就会有多个INODE Entry结构,需要多个页面进行存放,因此使用这两个链表来标记已经存满的INODE页和仍有空间的INODE页
-
-
XDES Entry:区描述信息,存储本组256个区对应的属性信息
- 这就是我们前面提到的每个区对应的结构,它们便存放此处。
- 一个XDES Entry是40个字节,每个组中维护了256个区,那么在此处(存放在每个组的第一个页面,这里是第一个组,页面类型是FSP_HDR,除了存放XDES Entry还存放了表空间的信息,后面所有组第一个页面都是XDES类型,显然不难看出就是专门用于存放XDES Entry的)就会存放256个XDES Entry结构,因为每个区对应的XDES Entry结构的地址是固定的,因此就可以很方便的找到区对应的XDES Entry
-
Empty Space:尚未使用的空间
-
File Trailer:页的通用结构,不再赘述
2、XDES页
除了第一个组,其余每组第一个页面都是XDES类型的页面,其中存放了这个组内所有区对应的XDES Entry。
与FSP_HDR类型的页面相比,XDES类型的页面除了没有File Space Header部分之外,其余部分都是一样的。
3、IBUF_BITMAP页
每个分组的第二个页面的类型都是IBUF_BITMAP,这种类型的页面中记录了一些有关Change Buffer的信息。
我们平时向表中插入一条记录,实际本质就是向每个索引对应的B+树插入记录。该记录首先插入聚簇索引页面,然后再插入每个二级索引页面。这些页面在表空间中随机分布,将会产生大量的随机I/O,严重影响性能,修改和删除也是同理。因此InnoDB引入了Change BUffer结构(本质上也是表空间中的一棵B+树,它的根节点存储在了系统表空间中),在修改非唯一二级索引页面时(原因可以自行了解关于Change Buffer的内容),如果该页面尚未被加载到内存中,那么该修改将先被暂时缓存到Change Buffer中,之后服务器空闲或者因为其他原因导致对应的页面加载到内存时,再将修改合并到对应页面。
4、INODE页
**第一个分组中的第三个页面的类型是INODE,前面提到的每个段对应的INODE Entry便存放在这个页中。**其组成如下:
- File Header:页的通用结构,不再赘述
- List Node for INODE Page List:通用链表节点,存储上一个INODE页面和下一个INODE页面的指针。就是前面FSP_HDR页面中提到的SEG_INODES_FULL链表和SEG_INODES_FREE链表节点
- INODE Entry:段描述信息,一个INODE Entry结构占192字节,一个页面中可以存储85个这样的结构
- Empty Space:尚未使用的空间
- File Trailer:页的通用结构,不再赘述
当我们每创建一个新的段时(创建索引就会创建段),都会创建一个与之对应的INODE Entry,其流程如下:
- 查看SEG_INODES_FREE链表是否为空,如果不为空则直接从链表中获取一个节点。即获取到一个仍有空闲空间的INODE类型的页面,然后把该INODE Entry结构放到该页面中。当该页面中无剩余空间时,就把页放到SEG_INODES_FULL链表中
- 如果SEG_INODES_FREE链表为空,则需要从表空间的FREE_FRAG链表中申请一个页面,并将该页面的类型修改为INODE,把该页面放到SEG_INODES_FREE链表中,同时把该INODE Entry结构放入页面
5、INDEX页
表空间中除了前面提到的每个组前几个固定的页面,剩下大多数存放的都是INDEX页,也就是B+树的节点。我们知道B+树中叶子节点存放的是数据,而非叶子节点存放的是索引。但其实无论是聚簇索引还是二级索引,无论是叶子节点还是非叶子节点,都是使用这个类型的页面。其结构如下:
-
File Header:页的通用结构,不再赘述(数据页之间没有必要是物理连续的,因为这个头部中有双向链表来维护页的顺序)
-
Page Header:
- PAGE_N_DIR_SLOTS:在Page Directory(页目录)中的slot(槽)数
- PAGE_HEAP_TOP:堆中第一个记录的指针(记录在页中是根据堆的形式存放的)
- PAGE_N_HEAP:堆中的记录数(占用两个字节,最高位表示是否为紧凑型记录)
- PAGE_FREE:指向可重用空间的首指针
- PAGE_GARBAGE:已删除记录的字节数,即行记录结构中 delete flag 为 1 的记录大小的总数
- PAGE_LAST_INSET:最后插入记录的位置
- PAGE_DIRECTION:最后插入的方向(PAGE_LEFT、PAGE_RIGHT等)
- PAGE_N_DIRECTION:一个方向连续插入记录的数量
- PAGE_N_RECS:该页中记录的数量(不包括最小和最大记录以及标记为删除的记录)
- PAGE_MAX_TRX_ID:当前页最大事务 ID,该值仅在 Secondary Index 定义
- PAGE_LEVEL:当前页在索引树中的位置,叶节点总是在第0层
- PAGE_INDEX_ID:索引ID,表示当前页属于哪个索引
- PAGE_BTR_SEG_LEAF:B+ 树数据页非叶节点所在段的segment header,该值仅在B+树的Root页中定义
- PAGE_BTR_SEG_TOP:B+ 树数据页所在段的segment header,该值仅在B+树的Root页中定义
-
Infimum 和 Supremum Records:最小记录和最大记录(两个虚拟的记录)
- 在 InnoDB 存储引擎中,每个数据页中共有两个虚拟的行记录,用来限定记录的边界
- Infimum 记录是比该页中任何主键值都要小的值,Supremum 指比任何可能大的值还要大的值,这两个值在页创建时被建立,并且在任何情况下不会被删除
- 记录比大小是通过主键值进行比较。虽然Infimum和Supremum没有主键值,但是默认Infimum就是最小记录,Supremum就是最大记录
-
User Record:实际存储行记录的内容
- 对于叶子节点而言,这里其实就是数据表中每一行的数据
- 对于非叶子节点,也就是索引而言,这里存放的其实也是一行一行的数据,不过行的构成是索引的值和其指向的页面的页号
-
Free Space:空闲空间,同样也是个链表数据结构。在一条记录被删除之后,该空间会被加入到空闲链表中
-
Page Directory:页目录
- 将所有正常的记录(包括Infimum和Supremum记录,但不包括移除到垃圾链表的记录)划分为几个组,每组的最后一条记录(组内最大的那条记录)的头信息中的n_owned属性表示该组内共有几条记录
- 将每个组最后一条记录在页面中的地址偏移量单独提取出来,按顺序存放。页目录中的这些地址偏移量称为槽
- InnoDB存储引擎的槽是一个稀疏目录,一个槽中可能包含多个记录。伪记录Infimum的n_owned值总是为1,Supremum的n_owned的取值范围为[1, 8],其他用户记录n_owned的取值范围为 [4,8]。当记录被插入或删除时需要对槽进行分裂或平衡的维护操作
- B+树索引本身并不能找到具体的一条记录,能找到的只是该记录所在的页。数据库把页载入到内存,然后通过Page Directory再进行二叉查找,找到具体的槽(分组),之后进入到分组中遍历槽内的记录
-
File Trailer:页的通用结构,不再赘述
我们都知道一个索引会产生两个段,分别是叶子节点段和非叶子节点段,而每个段都会对应一个INODE Entry结构。那我们怎么知道某个段对应哪个INODE Entry结构呢?**其实索引对应的段的INODE Entry结构就存放在这个索引INDEX页面的Page Header中的Page_BTR_SEG_LEAF(B+树叶子节点段的头部信息)和PAGE_BTR_SEG_TOP(B+树非叶子段的头部信息)属性中(只会在B+树的根页中定义)。**这两个属性其实都对应一个Segment Header的结构,具体结构如下:
- Space ID of the INODE Entry:INODE Entry结构所在的表空间ID
- Page Number of the INDOE Entry:INODE Entry结构所在的页面页号
- Byte Offset of the INODE Entry:INODE Entry结构在该页面中的偏移量
行记录的记录头信息:固定占用 5 个字节,40 位
预留位(1位)
预留位(1位)
deleted_flag(1位):该行是否已被删除
- 记录删除之后并不会从磁盘移除,因为移除还需要在磁盘上重新排列其他的记录,所以只将其标记为1
- 被删除掉的记录会形成一个垃圾链表,记录在这个链表中占用的空间称为可重用空间
min_rec_flag(1位):B+ 树每层非叶子节点中最小的目录项记录都会添加该标记
n_owned(4位):每个页的记录会被分组,分组的owner(组内最大记录)会记录该组的记录数量
heap_no(13位):索引堆中该条记录的排序记录
- 向表中插入的记录本质上来说都是放到User Records部分,这些记录一条一条紧密的排列在一起
- 把每一条记录(包括deleted_flag为1的记录)在堆中的相对位置称之为heap_no。在页中靠前的记录heap_no相对较小,靠后的记录heap_no相对较大。每申请一条新的记录的存储空间时,该记录比物理位置在它前面的那条记录的heap_no值大一
record_type(3位):0表示普通记录,1表示非叶子节点的目录项记录(即索引),2表示Infimum记录,3表示Supremum记录
next_record(16位):下一条记录的相对位置
- 下一跳记录指的并不是插入顺序中的下一条,而是按照主键值有小到大的顺序排列的下一条记录
- InnoDB始终会维护记录的一个单项链表,链表中的各个节点都是按照主键值从小到大的顺序连接起来的
- 目录项中的槽就是按照链表的顺序划分的,从而保证一个有序性
- 被删除的记录也会使用该属性连接形成垃圾链表
对于普通记录(叶子结点)和目录项记录(非叶子结点/索引),都是采用一样的行记录格式,只有如下部分有所差别:
- 目录项的record_type是1,普通记录是0
- 目录项记录只有主键值和页号两个列(二级索引有3个列:索引列的值、主键值和页号,先按照索引列排序,之后在按照主键值排序),而普通记录的列是用户自己定义的,可能包含很多了,还有InnoDB自己添加的隐藏列
- 只有目录项记录的min_rec_flag属性才可能为1,普通记录的都是0
六、系统表空间
在默认情况下,InnoDB会在数据目录下创建一个名为ibdata1、大小为12MB的文件,这个文件就是系统表空间在文件系统上的表示。这个文件是一个字扩展文件,当不够用时会自动自己增加文件大小。
前面我们知道了每个独立表空间中都划分成了一些组,每个组包含了256个区,组的前几个页面为保存有区对应的XDES Entry结构,而表空间的第一个页面还存放了直属于表空间的各种类型的区的链表以及表中各个段对应的INODE Entry结构。那么此时我们如果在添加数据时需要申请页面,就可以根据当前所操作的这棵B+树,找到其对应的段的INODE Entry,如果其维护的零碎的页面还没有超过32个,那么就会从直属于表空间的区的链表中找到合适的区去申请页面。如果已经超过了,那么就从直属于这个段的区的链表中找到合适的区申请页面。
前面操作的前提都是我们需要能拿到我们要操作的B+树的根节点,那怎么找到要插入的数据所在的B+树呢。在独立表空间中其实并没有维护哪个索引就是对应哪个数据页之类的信息,这部分信息是存储在系统表空间中的。
系统表空间和独立表空间的前三个页面的类型是一致的(FSP_HDR、XDES、IBUF_BITMAP),后续其他组的前两个页面类型也是一致的(XDES、IBUF_BITMAP)。但是页号3-7的页面(即第一个组的第四到第八个页面)是系统表空间特有的,如下:
- 页号3(SYS):Insert Buffer Header,存放Change Buffer的头部信息
- 页号4(INDEX):Insert Buffer Root,存放Change Buffer的根页面
- 页号5(TRX_SYS):Transaction System,事务系统的相关信息
- 页号6(SYS):First Rollback Segment,第一个回滚段的信息
- 页号7(SYS):Data Dictionary Header,数据字典头部信息
除了这几个记录系统属性的页面外,系统表空间第二个区和第三个区(即extent1和exten2,也就是页号从64-191这128个页面)称为Doublewrite Buffer(双写缓冲区)。
为了解决我们上面提到的问题,这里我们主要了解数据字典。
当我们向一个表中插入一条记录时,MySQL先要校验插入语句所对应的表是否存在,以及插入的列和表中的列是否符合,如果语法没有问题,还需要知道表的聚簇索引和所有二级索引对应的根页面是哪个表空间的哪个页面,然后再把记录插入到对应索引的B+树中。所以我们除了在独立表空间中保存用户插入的数据外,还需要在系统表空间中保存许多额外的信息:
- 某个表属于哪个表空间,表中有多少列
- 表对应的每一个列的类型是什么
- 该表有多少个索引,每个索引对应哪些字段,该索引对应的根页面在表空间的哪个页面
- 该表有那些外键,对应哪些列
- 某个表空间对应的文件系统上的文件路径是什么
上面这些信息并不是使用iinsert语句插入的用户数据,而是为了更好地管理用户数据而不得不引入的一些额外数据,也称为元数据。InnoDB存储引擎特意定义了一系列的内部系统表来记录这些元数据,具体如下:
-
SYS_TABLES:整个InnoDB存储引擎中所有表的信息
- 表的名称
- 表的ID
- 表的列数
- 表的类型(记录了一些文件格式、行格式、压缩等信息)
- 表的一些额外属性
- 表所属表空间的ID
-
SYS_COLUMNS:整个InnoDB存储引擎中所有列的信息
- 所属表的ID
- 列在表中是第几列
- 列的名称
- 列的类型
- 精确数据类型(修饰主数据类型,如是否允许NULL、是否允许负数)
- 列最多占用字节数
- 列的精度
-
SYS_INDEXES:整个InnoDB存储引擎中所有索引的信息
- 所属表的ID
- 索引的ID(在InnoDB存储引擎中,每个索引都有一个唯一的ID)
- 索引名称
- 索引包含列数
- 索引的类型
- 索引根页面所在表空间的ID
- 索引跟页面所在的页面号
- 页面记录被删除到某个比例时尝试与邻居页面合并
-
SYS_FIELDS:整个InnoDB存储引擎中所有索引对应的列的信息
- 列所属索引ID
- 列在索引列中是第几列
- 列的名称
-
SYS_FOREIGN:整个InnoDB存储引擎中所有外键的信息
-
SYS_FOREIGN_COLS:整个InnoDB存储引擎中所有外键对应的列的信息
-
SYS_TABLESPACES:整个InnoDB存储引擎中所有表空间的信息
-
SYS_DATAFILES:整个InnoDB存储引擎中所有表空间对应的文件系统的文件路径信息
-
SYS_VIRTUAL:整个InnoDB存储引擎中所有虚拟生成的列的信息
这些系统表也被称为数据字典,它们都是以B+树的形式保存在系统表空间的某些页面中。其中SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS这四个表尤为重要,称为基本系统表。
只要有了上述4个基本系统表,就可以获取其他系统表以及用户定义的表的所有元数据。也就是说这4个表示表中之表。
那么这四个表的元数据(比如说它们有哪些页,那些索引等信息)去哪获取呢,这里只能硬编码到代码中了,然后将前面讲到的系统表空间的页号为7的页面,即Data Dictionary Header用来保存数据字典头部信息。这个页中记录了这4个基本系统表的聚簇索引和二级索引对应的B+树的位置以及一些全局属性,具体如下:
-
File Header:页的通用结构,不再赘述
-
Data Dictionary Header:数据字典头部,记录一些基本系统表的根位置以及InnoDB存储引擎的一些全局信息
-
Max Row ID:隐藏列row_id的值,全局共享的
- 无论哪个拥有row_id列的表插入一条记录,该记录的row_id列的值就是Max Row ID对应的值,然后再把Max Row ID对应的值加一
-
Max Table ID:新建表时使用的ID,全局共享的
-
Max Index ID:新建索引时使用的ID,全局共享的
-
Max Space ID:新建表空间时使用的ID,全局共享的
-
SYS_TABLES表聚簇索引的根页面的页号
-
SYS_TABLES表二级索引的根页面的页号
-
SYS_COLUMS表聚簇索引的根页面的页号
-
SYS_INDEXES表聚簇索引的根页面的页号
-
SYS_FIELDS表聚簇索引的根页面的页号
-
-
Unused:未使用
-
Segment Header:段头部,记录了本页面所在段对应的INODE Entry位置信息
- InnoDB把有关数据字典的信息当成一个段来分配存储空间,但是该段要记录的字典信息非常少,所以只用到了这一个碎片页
-
Empty Space:尚未使用的空间
-
File Trailer:页的通用结构,不再赘述
用户其实不能直接访问InnoDB的这些内部系统表,除非直接去解析系统表空间对应的文件系统的文件,不过InnoDB在系统数据库information_schema提供了一些以INNODB_SYS开头的表以供访问。这些INNODB_SYS开头的表并非真正的内部系统表,而是存储引擎启动时读取系统表后填充进去的,所以它们与系统表的字段并不完全一样。