Innodb 的数据存储与索引构建

59 阅读5分钟

原文链接:关于Mysql数据存储,你了解多少?

Innodb 数据怎么存储?

Innodb 的存储结构:

  • Segment:
  • extent:
  • page: 磁盘和内存交换的基本单位,默认 16KB
  • row: image.png

Row

记录在磁盘的存放方式也被称为「行格式」或者记录格式。行格式主要包含以下几种:

  • compact
  • redundant
  • dynamic
  • compressed

1. compact

compact 的存储结构如下:

  • 变长字段长度列表:存储变长数据类型真实存储的字节长度
  • null 值列表:统一管理所有可以为 null 的列,分别用 0/1 代表对应列是否为空
    • 目的:节省 NULL 的存储空间
  • 记录头信息:包含记录的状态、记录数、记录类型等信息

image.png

2. redundant

与 compact 相比,缺少了「变长字段列表」以及「NULL值列表」,增加了所有真实数据的偏移地址

3. dynamic

和 compact 基本相同,对于溢出页的处理会有一些差异。

溢出页:存储空间较大的列,单页无法存储,需要将部分数据存在其他页中

compact 中溢出页会先存储 768字节的数据,20个字节存储剩余数据存储页的地址。dynamic 会直接存储20字节的溢出页地址,不再额外记录一部分的数据。

image.png

4. compressed

在 dynamic 的基础上增加了压缩处理,能存储更多大长度类型的数据,但是性能并不友好。

Page

页中会有单独的信息标识当前的空闲空间,以及用户记录。当空闲不足时,再有新的记录就会申请新的页。

  • Free Spage: 页中未使用的空间
  • User Records: 实际存储的行记录内容
  • Page Directory: 页中一些记录的相对位置
  • infimun/supermun: 最 小/大 记录的位置,是虚拟的行记录
  • File Header: 每一页都有一个唯一的页号,还会记录上一页和下一页的页号。页之间是一个双向链表的结构

image.png

其中 user records 中的记录按照 id 从小到大的顺序串联成了一个单向链表:

  • infimun 指向 id 最小记录
  • 最大记录 指向 supermun

记录中有一个 delete_mask 标记当前数据是否被删除,执行删除操作后数据会先标记,但不会立即移除,因为重新排列需要比较大的性能消耗。所有删除的数据组成了一个垃圾链表,这些占用空间在后面可能会直接被新数据覆盖

image.png

单向链表的检索效率不高,最差需要遍历页中的所有数据,因此增加了 页目录(Page Directory) 来提升效率。页目录的主要模式如下:

  • 增加了一层的概念,每个槽之间会有不超过8条的数据,超过8条则再次拆分
  • 按照主键进行排序

image.png

这样在页中查找指定主键记录的过程:

  1. 通过二分确认该记录所在的槽,并找到该槽所在分组中 主键 最大的记录
  2. 如果定位到数据所在的槽分组,则向上找一个槽位,定位到最后一行,通过 next_record 索引遍历目的槽中的记录

B+索引怎么提升检索效率?

回顾一下,innodb 数据页之间通过双向链表相连,页内数据按照「主键」排序组成单向链表,每个数据页也会维护一个「页目录」通过二分提升效率。

问题:

  • 刚才聊的都是在单页中查找,那么如何找到数据所在的页?
  • 页目录是针对主键的,那么查询条件不是主键怎么办?

怎么构建索引?

预设:下一个页的主键必须大于上一个页中的主键。

假设一个数据页最多有3条数据,且当前数据页中已经达到了 3 条记录的上限,新增一条记录之后会新申请一个页,但是可能会破坏页之间主键大小的预设,如下图: image.png 此时,就需要重新移动记录,把记录4移动到页2,把记录3移动到页1,这个过程就是「页分裂image.png 但是实际存储时,页之间的编号并不连续,因此需要新增一个目录来记录页的位置和数据范围。如下图,目录1记录了页1的页号和最小值,目录2记录了页23的页号和最小值。我们查找数据时:

  • 在目录中根据二分快速找到对应的页
  • 再根据二分找到对应的槽,再去遍历数据 image.png 这个目录就叫索引

Innodb 中的索引怎么构建?

Innodb 使用 页 来存储目录以及具体的目录项,对应的存储结构如下。其中页17存储了目录,页1、页23和页8存储了数据。 image.png 如果数据太多,单页无法存储所有的目录?

  • 那就再加一页,页之间通过双向链表相连 如果数据继续增加,目录页也变得足够多?
  • 那就再增加一层“目录页”的“目录页”

最终的结构如下图所示:

  • 每个页节点都维护下一层页的页码和最小id,页之间的 id 有序
  • 只有底层叶节点真正存储了用户数据 image.png

而这个索引就是B+树的结构,也就是我们常说的“聚簇索引”,是 innodb 自动创建的索引。那么非聚簇索引呢?

  • 使用非id列进行记录和排序
  • 叶节点存储的不是完整的用户记录,而是「索引列」+「主键」 image.png

索引虽好,不要贪杯

  • 时间上:每次数据的变更,都需要修改各个B+树的索引,还伴随着页分裂
  • 空间上:每一个索引都需要单独建立一个B+树