一、概述
- 平时仅仅知道的是 Innodb 底层是一个 B+ 树,对于其再磁盘上的具体存储形式,一直没有很深入的理解,今天看了一篇博客,做一次记录
二、页
- 一个磁盘或文件的容量也是非常可观,
innodb
把文件划分成一个个大小相等的存储块,这些块也被称为页;
- 页的默认大小是 16K,磁盘读取是4K读取的,相当于一次读取至少要读取4个块;
- 另一方面由于缓存的概念 Page chache 存在,所以一次读取往往会把前后的一些数据同时拉去到内存中
三、区
-
cpu在使用的数据时,下一步也会大概率使用逻辑上相邻的数据。因此为了提高数据读操作的性能,innodb把逻辑上相临的数据尽可能在物理上也存储在相邻的页中;为了实现这一目标,Innodb引入了区/簇的概念;
-
默认情况下 64 个连续的页是一个区 恰好等于 1M= 64 * 16K
四、段
- innodb中把这些记录具有相关性区的存储空间状态的管理信息称为段实体,段实体所管理的区的总和称为段,段的目的是管理区的使用情况以及为数据分配空间时,提供空间存储状态。
- 在InnoDB中会为每个索引分配两个段,一个段对应B+树的非叶子节点(索引段),另一个对应叶子节点(数据段)。
1、总结
- 简单来说,
- 页的概念,是用于最小的磁盘io
- 区的概念,是用于将逻辑上连续的数据尽量实现物理连续
- 段的概念,是用于管理区的使用情况,
五、总体架构
- 索引数据与业务行数据分别具有不同的数据结构,因此它们被分开存储,非叶子节点的索引数据存储在一个段中,叶子节点的业务数据存储在另一个段,对应的它们也分别存储在不同结构的区和页中。
1、逻辑结构
2、物理结构
整体架构
六、相关细节
页
-
任何一个页都由页头、页身和页尾组成。
-
页的大小默认是 16kB,可用修改
- 页= 4,6,16 KB 时候; 区 = 1MB
- 页 = 32KB和 64KB 时候 区 = 2或者 4MB
-
一个page默认16KB,而段和区对应的指针数据量并不大,因此只需要部分头信息就可以维护。而剩下的大部分空间,则用来存储当前表空间拥有的部分发区实体信息。
当一条新记录被插入到InnoDB clustered index中时,InnoDB预留page的1/16的空间以备将来插入或者更新索引记录。
-
对于顺序写入的索引(无论是递增或是递减,顺序的就行),索引叶点可以达到15/16满。
-
如果是随机的索引写入行为,叶点只会达到1/2到15/16满。当叶点填充在1/2以下满,或是被删除到1/2下满时,Innodb会缩短索引树,试图释放该叶点,该叶点可以被继续写入数据。
-
File Header
- Page中的的File Header保存了两个指针,分别指向前一个Page和后一个Page,头部还有Page的类型信息和用来唯一标识Page的编号。根据这两个指针,可以想象出Page链接起来就是一个双向链表的结构。
- User Record
- 数据和索引的存储都位于Page的User Records部分。
- User Records由一条一条的Record组成。
- 在一个Page内部,单链表的头尾由固定内容的两条虚拟记录来表示,
- 字符串形式的”Infimum”代表开头,
- ”Supremum”代表结尾,本身并不存储数据。
- 首次创建索引时,InnoDB会在根页面中自动设置,并且永远不会删除它们。这两个用来代表开头结尾的Record存储在System Records里,这个System Records和User Records是两个平行的段。
- InnoDB的Record存在于4种不同的节点里,它们分别是
- 1主键索引树非叶节点
- 2主键索引树叶子节点
- 3辅助键索引树非叶节点
- 4辅助键索引树叶子节点。
- 这4种节点的Record格式有一些差异,但是它们都存储着Next指针指向下一个Record。
- User Record在Page内以单链表的形式存在,随着新数据的插入和旧数据的删除,数据物理顺序会变得混乱,但他们依然保持着逻辑上的先后顺序(Next指针)。通过记录头中的next record可以串联得到一个可用空间链表。
- 这里要注意,B+树索引并不能找到一个键值对应的具体行。b+树索引
只能查到被查找数据行所在的页
,然后数据库通过把页读入内存,再在内存中查找,最后得到结果。 - 在Page内从”Infimum”节点开始遍历单链表(这种遍历往往会被优化),如果找到该键则成功返回。如果记录到达了”supremum”,说明当前Page里没有合适的键,这时要借助Page的Next Page指针,跳转到下一个Page继续从”Infimum”开始逐个查找。
页类数据如何保持有序?
-
innodb页面记录都已经通过一个链表维护起来,并且从链头到链尾依次增大(普通方法2),是无法使用二分查找。
-
Slot指向的记录依然保证着从右往左依次有序的特性,我们使用rec[S2]表示S2指向的记录,那么rec[S2]>rec[S1]。另外,本文称两个slot之间的记录称为一个Slot 支链,如图3虚线圈住的部分,表示S2指向的Slot支链。Innodb维护的Slot支链高度为4-8,如果一个支链的高度超过或不足,会导致相应的支链拆分和合并操作。
如何查询?
如果在页面中查询记录r1(为简单起见,假设索引键是唯一的)。首先通过二分查找定位Slot号X,满足
rec[X-1]< r1 <= rec[X]
那么记录r1要么不存在,要么就在Slot支链X中。接着就是遍历这条支链,找到真正记录。但支链的搜索只能一一遍历,不能使用二分查找。
如何插入?
首先通过查询的方式确定插入的Slot支链和插入位置,在自由空间链表或未分配空间中获得空间并写记录内容,slot支链高度加1,同时维护好原链表的关系。
插入记录后,如果Slot支链高度超过8,那么就将该支链拆分为两个子链,同时多申请一个slot(平移此slot及其后面的空间)。
从存取效率上,不能完全的二分查找,需要使用二分查找+顺序遍历
区
-
区/簇本身没有编号,但区/簇像页一样,也是从文件第一个字节开始连续分配的。同时,每隔256个区/簇的第一个区的第一页就是这256个区/簇的索引页,即XDES page。
-
为了保证区中页的连续性,扩展的时候InnoDB存储引擎一次从磁盘申请4~5个区。