InnoDB数据结构

56 阅读9分钟

常见数据结构类型

Hash结构, 二叉搜索树,AVL树,B-Tree,B+Tree,R树

全表遍历

InnoDB索引-数据结构原理-全表遍历.png

时间复杂度:O(n)

  • 进行数据查找时,先看查询条件是否命中某条索引,符合则通过索引查找相关数据,不符合则需要全表扫描,逐条查找直到找到与条件符合的记录。
  • 摆臂耗时:无索引的情况下,数据分布在硬盘不同的位置上,读取数据摆臂需要前后摆动查找数据,该操作十分耗时。
  • IO耗时:即使开辟连续的空间将数据顺序存放,那么也需要将数据逐一加载到内存中比较,直到找到与条件符合的记录才停止加载比较,IO操作频繁非常耗时。

Hash结构

  • Hash本身是一个函数,又被称为散列函数,它可以帮助我们大幅提升检索数据的效率。
  • Hash算法是通过某种确定性的算法(比如MD5、SHA1、SHA2、SHA3)将输入转变为输出。相同的输入永远可以得到相同的输出,假设输入内容有微小偏差,在输出中通常会有不同的结果。
  • 举例:如果你想要验证两个文件是否相同,那么你不需要把两份文件直接拿来比对,只需要让对方把Hash函数计算得到的结果告诉你即可,然后在本地同样对文件进行Hash函数的运算,最后通过比较这两个Hash函数的结果是否相同,就可以知道这两个文件是否相同。

加速查找速度的数据结构,常见的有两类

  • 树,例如平衡二叉搜索树,查询/插入修改删除的平均时间复杂度都是O(log2N)
  • 哈希,例如 Hashmap,查询/插入/修改/删除的平均时间复杂度都是O(1)
  • 采用Hash进行检索效率非常高,基本上一次检索就可以找到数据,而B+树需要自顶向下依次查找,多次访问节点才能找到数据,中间需要多次IO操作,从效率来说Hash比B+树更快。

Hash结构效率高,那为什么索引结构要设计成树型呢?

  • 原因1:Hash索引仅能满足(=)(<>)和IN査询。如果进行范围査询,哈希型的索引,时间复杂度会退化为o(n);而树型的“有序”特性,依然能够保持O(log2N)的高效率。
  • 原因2:Hash索引还有一个缺陷,数据的存储是没有顺序的,在 ORDER BY的情况下,使用Hash索引还需要对数据重新排序。
  • 原因3:对于联合索引的情况,Hash值是将联合索引键合并后一起来计算的,无法对单独的一个键或者几个索引键进行查询。
  • 原因4:对于等值查询来说,通常Hash索引的效率更高,不过也存在一种情况,就是索引列的重复值如果很多,效率就会降低。这是因为遇到Hash冲突时,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时。所以,Hash索引通常不会用到重复值多的列上,比如列为性别、年龄的情况等。
  • InnoDB本身不支持Hash索引,但是提供自适应Hash索引( Adaptive Hash Index)。什么情况下才会使用自适应Hash索引呢?如果某个数据经常被访问,当满足一定条件的时候,就会将这个数据页的地址存放到Hash表中。这样下次查询的时候,就可以直接找到这个页面的所在位置。这样让B+树也具备了Hash索引的优点。

查看Hash自适应索引

show variables like '%adaptive_hash_index';

二叉树/二叉搜索树

InnoDB索引-数据结构原理-二叉搜索树.png

时间复杂度:O( log2 (N) )

  • 数据以二叉树的结构存储到硬盘,每个节点存储的是(K, V)结构
  • 对字段Col2添加了索引,就相当于在硬盘上为Col2维护了一个索引的数据结构,即这个二叉搜索树。
  • 二叉搜索树的每个结点存储的是(K, V)结构,key是col2, value是该key所在行的文件指针(地址)。比如:该二叉搜索树的根节点就是:(34, 8×87)。现在对col2添加了索引,这时再去查找Col2=89这条记录的时候会先去查找该二叉搜索树(二叉树的遍历査找)。读34到内存,89>34;继续右侧数据,读89到内存,89=89;找到数据返回。找到之后就根据当前结点的 value快速定位到要查找的记录对应的地址。我们可以发现,只需要查找两次就可以定位到记录的地址,查询速度就提高了。

二叉搜索树的特点

  • 一个节点只能有两个子节点,也就是一个节点度不能超过2
  • 左子节点<本节点;右子节点>=本节点;比我大的向右,比我小的向左

二叉搜索树的缺点 InnoDB索引-数据结构原理-二叉搜索树极端情况.png

  • 存在特殊的情况,就是有时候二叉树的深度非常大,创造出来的二分搜索树如上图式。
  • 上图的性能上已经退化成了一条链表,查找数据的时间复杂度变成了O(n),即查91每个元素都要遍历对比。

AVL树

InnoDB索引-数据结构原理-平衡二叉树.png

  • 为了解决上面二叉查找树退化成链表的问题,人们提出了平衡二叉搜索树( Balanced Binary Tree),又称为AML树(有别于AⅥL算法),它在二叉搜索树的基础上增加了约束,具有以下性质:
  • 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

InnoDB索引-数据结构原理-平衡三叉树.png

  • 平衡M叉树:降低树的高度,减少IO

B-Tree(B树)

InnoDB索引-数据结构原理-B树.png

  • B树的英文是 Balance tree,也就是多路平衡查找树。简写为B-Tree注意横杠表示这两个单词连起来的意思,不是减号)。它的高度远小于平衡二叉树的高度。
  • 非子节点也存储完整的数据记录(17,35)
  • P1数据都小于17,P2在17和35中间,P3数据大于35

InnoDB索引-数据结构原理-B树结构.png

  • B树结构
  • B树在插入和删除节点的时候如果导致树不平衡,就通过自动调整节点的位置来保持树的自平衡。
  • 关键字集合分布在整棵树中,即叶子节点和非叶子节点都存放数据,搜索有可能在非叶子节点结束。
  • 其搜索性能等价于在关键字全集内做一次二分查找

B+Tree

B+树也是一种多路搜索树,基于B树做出了改进,主流的DBMS都支持B+树的索引方式,比如MSQL。相比于B-Tree,B+Tree适合文件索引系统。

行记录

 -- 查询innodb默认行格式
 select @@innodb_default_row_format;

InnoDB索引-数据结构原理-行记录格式-简略.png

  • record_type:记录头信息的一项属性,表示记录的类型,θ表示普通记录、1目录项记录、2表示最小记录、3表示最大记录。
  • next record:记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。
  • 各个列的值:这里只记录在 index demo表中的三个列,分别是c1、c2和c3。
  • 其他信息:除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息,将记录格式示意图的其他信息项暂时去掉并把它竖起来的效果就是这样。

数据页

 -- 数据页大小查询
 show variables like '%innodb_page_size';

数据页简略结构

InnoDB索引-数据结构原理-数据页-简略.png

  • 数据页大小:16K物理内存
  • 数据页:一个具体的地址(随机无讲究)

页分裂/记录移动

InnoDB索引-数据结构原理-页分裂.png

  • 数据页存储达到16K后,再进行存储新纪录会开辟新的数据页
  • 新数据页存储新纪录后,会根据索引进行排序(图为对主键进行排序)
  • 数据页之间通过双向链表实现逻辑连续

索引迭代一:数据页目录项

数据页做的简易目录如下,这个目录有一个别名,叫索引

InnoDB索引-数据结构原理-数据页目录项.png

  • 16K的数据页编号在物理存储上可能是不连续的
  • 为快速定位某些记录所在的数据页,需要建立一个数据页目录项

InnoDB索引-数据结构原理-数据页目录项1.png

  • key:数据页的用户记录中最小的主键值
  • page_no:页号
  • 还需要存储主键值确保唯一性

InnoDB索引-数据结构原理-数据页目录项2.png

  • 目录项记录间通过单项链表实现逻辑连续,应对增删记录导致的目录变动。
  • record_type:用于区分数据页和目录页。

索引迭代二:多个目录项记录的页

InnoDB索引-数据结构原理-多个目录项记录的页.png

索引迭代三:目录项记录页的数据页

InnoDB索引-数据结构原理-目录项记录页的数据页.png

  • 数据页需要存储主键值确保唯一性

B+Tree简易结构

InnoDB索引-数据结构原理-B+Tree简易结构.png

  • 数据页与数据页之间通过双向链表连接
  • 数据页内,行记录通过单向链表连接
  • B+Tree通常不超过4层
    • 树的层次越低,IO次数越少;
    • 每个数据页存的用户记录:每条记录160byte * 100psc = 16Kb
    • 每个目录页存的目录项:16byte * 1000pcs = 16Kb
    • B+Tree 4层最多存放:1000 * 1000 * 1000 * 100 = 1万亿
    • 阿里开发规范建议:单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表

注意事项

根页面的位置万年不动

  • 每当为某个表创建一个B+树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一个根节点页面。最开始表中没有数据的时候,每个B+树索引对应的根节点中既没有用户记录,也没有目录项记录。
  • 随后向表中插入用户记录时,先把用户记录存储到这个根节点中。
  • 当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页a中,然后对这个新页进行页分裂的操作,得到另一个新页,比如页b。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页a或者页b中,而根节点便升级为存储目录项记录的页。
  • 这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方,然后凡是 Innodb存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。

内节点中目录项记录的唯一性

InnoDB索引-数据结构原理-目录项结构.png

  • 使用非主键列作为索引时,列存储值可能重复,在页分裂时无法确定存储在哪个数据页。
  • 因此目录项也需要保存主键值确保唯一性。

一个页面最少存储2条记录

  • 一个B+树只需要很少的层级就可以轻松存储数亿条记录,查询速度相当不错!这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录。那如果个大的目录中只存放一个子目录是个啥效果呢?那就是目录层级非常非常非常多,而且最后的那个存放真实数据的目录中只能存放一条记录。费了半天劲只能存放一条真实的用户记录?所以 Innodb的一个数据页至少可以存放两条记录。