Mysql的磁盘存储结构

1,315 阅读6分钟

一、概述

  • 平时仅仅知道的是 Innodb 底层是一个 B+ 树,对于其再磁盘上的具体存储形式,一直没有很深入的理解,今天看了一篇博客,做一次记录

二、页

  • 一个磁盘或文件的容量也是非常可观,innodb把文件划分成一个个大小相等的存储块,这些块也被称为

image.png

  • 页的默认大小是 16K,磁盘读取是4K读取的,相当于一次读取至少要读取4个块;
  • 另一方面由于缓存的概念 Page chache 存在,所以一次读取往往会把前后的一些数据同时拉去到内存中

三、区

  • cpu在使用的数据时,下一步也会大概率使用逻辑上相邻的数据。因此为了提高数据读操作的性能,innodb把逻辑上相临的数据尽可能在物理上也存储在相邻的页中;为了实现这一目标,Innodb引入了区/簇的概念;

  • 默认情况下 64 个连续的页是一个区 恰好等于 1M= 64 * 16K

image.png

四、段

  • innodb中把这些记录具有相关性区的存储空间状态的管理信息称为段实体,段实体所管理的区的总和称为段,段的目的是管理区的使用情况以及为数据分配空间时,提供空间存储状态。
  • 在InnoDB中会为每个索引分配两个段,一个段对应B+树的非叶子节点(索引段),另一个对应叶子节点(数据段)。

1、总结

  • 简单来说,
    • 页的概念,是用于最小的磁盘io
    • 区的概念,是用于将逻辑上连续的数据尽量实现物理连续
    • 段的概念,是用于管理区的使用情况,

五、总体架构

  • 索引数据与业务行数据分别具有不同的数据结构,因此它们被分开存储,非叶子节点的索引数据存储在一个段中,叶子节点的业务数据存储在另一个段,对应的它们也分别存储在不同结构的区和页中。

1、逻辑结构

image.png

2、物理结构

image.png

整体架构

image.png

六、相关细节

  • 任何一个页都由页头、页身和页尾组成。

  • 页的大小默认是 16kB,可用修改

    • 页= 4,6,16 KB 时候; 区 = 1MB
    • 页 = 32KB和 64KB 时候 区 = 2或者 4MB
  • 一个page默认16KB,而段和区对应的指针数据量并不大,因此只需要部分头信息就可以维护。而剩下的大部分空间,则用来存储当前表空间拥有的部分发区实体信息

image.png

当一条新记录被插入到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链接起来就是一个双向链表的结构。

image.png

  • 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可以串联得到一个可用空间链表。

image.png

  • 这里要注意,B+树索引并不能找到一个键值对应的具体行。b+树索引只能查到被查找数据行所在的页,然后数据库通过把页读入内存,再在内存中查找,最后得到结果。
  • 在Page内从”Infimum”节点开始遍历单链表(这种遍历往往会被优化),如果找到该键则成功返回。如果记录到达了”supremum”,说明当前Page里没有合适的键,这时要借助Page的Next Page指针,跳转到下一个Page继续从”Infimum”开始逐个查找。

image.png

image.png

页类数据如何保持有序?

image.png

  • 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个区。

参考