关于Mysql索引你一定要知道的(上)

1,172 阅读9分钟

关于Mysql索引你一定要知道的

导图

索引模型

Hash

简介

Hash索引是MEMORY引擎默认使用的索引结构,也只有MEMORY引擎可以显式使用Hash索引,实际上就是HashTable,key-value结构。同时也是用拉链法解决hash冲突,这里不展开讨论。

结构

iShot2021-07-04 11.58.14.png

这里需要注意的是HashTable的Value里面并不是直接存储的单行数据而是存的是指针。

特点

  1. 在哈希索引中没有存索引字段的信息,所以并不能通过索引值来减少读取行数
  2. 哈希索引不是有序的,所以并不能做排序
  3. 哈希所以不支持索引匹配查找,比如说b+树索引能够使用最左匹配原则去使用联合索引,而哈希索引不能。也就是说在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。
  4. 哈希索引进行检索效率非常高,基本上一次检索就可以找到数据,从检索次数上来说比B+树索引要少得多
  5. 只支持等值查询,范围查询不支持
  6. 当哈希碰撞过多的时候,索引的维护代价很高

B树

简介

如果我们要了解B+树,就一定要先了解B树,B树是为磁盘等直接存储的辅助存储设备而设计的一种平衡搜索树,它的节点可以存储多个关键字,并且它是一个多叉树,一个B树满足以下条件:

先解释几个词:

  • 关键字:每个节点存储的数据
  • x:每个节点存储关键字的数量
  • m:树的高度

定义:

  1. 是一颗有根树
  2. 每个节点包含x+1个指向孩子节点的指针
  3. 每个节点的所存储的关键字以非降序排列,每个数据的左子树中的所有数据都小于它,而右子树中的所有数据都大于它。
  4. 每个叶节点都有相同的深度,即树的高度,也可以说成叶结点都位于同一层
  5. 每个节点至多有m-1个数据
  6. 根节点最少可以只有1个数据

数据结构

如图这是一个3阶B树 iShot2021-07-04 11.59.08.png

插入删除

B树的插入主要涉及三个步骤,查询、插入、分裂。

  • 查询:这里指的是找到插入位置,这里的查找和二叉搜索树的查找类似
  • 插入:关键字插入过程
  • 分裂:为了保持B树的结构,当节点关键字数量达到阈值,就会产生分裂动作,中间关键字提升到父节点,其他节点生成两棵新子树

B树的删除其实和插入非常相似,只是更加复杂一点,因为删除可以从任意节点删除,而并非只能从叶子节点删除。所以就涉及到删除后保持结构定义的过程。这里就不再赘述删除具体的流程,比较复杂。建议大家去看一下《算法导论》286页,有详细的描述。

动画

这里可以看到 Max. Degree = 3 表示是一个三阶B树,所以他的关键字最多只能有三个。

  1. 当我们插入1,树是空的,所以直接插入。
  2. 插入3,由于3大于1,并且当前节点关键字小于3,所以放在同一个节点1后面的位置.
  3. 插入5,5大于3所以在3后面,当前节点的关键字=3,触发分裂,中间关键字3向上升入根节点,1,5生成两个新子树
  4. 插入4,4大于根节点3,所以往右边查找,小于5,并且节点关键字数小于3
  5. 删除1,找到1的位置,删除,发现左子树为空了,不满足定义,004关键字上升成为根结点,003,005分别为左右节点。

这时候如果我们再插入1,就会发现,结构并不会恢复成之前的样子。

查询

其实从刚刚的插入删除的演示中就已经可以看出查询的过程,搜索一颗B树和搜索一颗二叉搜索树类似,不同的就在于每个节点的分支选择是多叉的,而不一定是二叉。

为什么B树(N叉)适合做索引

为了探究这个问题我们首先应该了解几个知识

  • 索引的存储

索引就是一种数据结构,需要存储,并且索引也不单单在内存中,需要存储到磁盘中。

  • 影响数据库查询速度的因素

在我之前的博文中提到过,实际上数据库查询数据是先查主存,如果不存在,才去磁盘中查询,然后带到主存里来。这里在磁盘查询的过程中会涉及到磁盘的io,实际上如果不考虑从主存中查询,最影响数据库查询速度的就是磁盘IO,因此尽量减少磁盘IO就可以显著的提升数据的查询速度。

  • 预读机制

传统的磁盘读取依靠的是机械运动,IO的耗时就分为寻道时间、旋转延迟、传输时间三个部分。**SSD则不同。**为了减少io的消耗,采用来预读的机制。当访问一个地址数据的时候,与其相邻的数据很快也会被访问到。每次磁盘IO读取的数据我们称之为一页(page)。一页的大小与操作系统有关,一般为4k或者8k。这也就意味着读取一页内数据的时候,实际上发生了一次磁盘IO。

通过上面几点,我们就可以知道,索引会写盘,查询索引的过程就是查询节点的过程如果不在内存中就会去查找磁盘,产生磁盘IO,那么索引的查询速度大部分就取决于每次查询需要访问的数据页。我们可以假设一棵 100 万节点的平衡二叉树,树高 20。一次查询可能需要访问 20 个数据块。而如果使用b树的话就取决于我们N叉树的N的大小。当然,这里B+树也是同理的。

B+树

简介

B+树其实就是B树和顺序索引访问方法演化而来的,在这里就不赘述B树和B+树的区别了,大家只要知道B树的每个结点都存储了key和data,B+树的data存储在叶子节点上。

结构

B+树其实就是B树的变体,和B树很相似。如图是一个三阶B+树。

插入和删除

从图中我们可以和清楚的看到,实际上B+树的插入和删除的过程和B树基本一致,唯一区别是,B+树通过一个双向链表把叶子节点链接起来,在维护树的时候同样需要维护这个双向链表,这里也不仔细去说详细的插入删除的算法。详见《Mysql技术内幕 InnoDB存储引擎》 P202

查询

查询过程其实也是接近的,区别只是B+树的节点没有存储data,所以实际上是查到链表中。

区别

  1. B+树改进了B树, 让非叶子结点只作索引使用, 去掉了其中指向data record的指针, 使得每个结点中能够存放更多的key, 因此能有更大的扇出数。
  2. 叶子结点使用链表形式连接。

非叶子节点不存储数据就意味着存放同样多的key, 树的层高能进一步被压缩, 使得检索的时间更短。并且叶子结点是链表形式,能更快的进行顺序遍历。

聚簇索引(clustered index)

也就是主键索引,叶子节点存储的是整行的数据,并且数据也是依据主键索引进行排序,主键索引一张表只能有一个也是因为数据只能按照一个索引进行排序。聚簇索引附带唯一性约束。

非聚簇索引(secondary index)

也叫辅助索引,叶子节点并不存放整行数据,而是存储主键的值。要查找到完整数据需要回表。

唯一索引

用来确保表中没有两个数据行具有完全相同的键值一般我们使用它来帮助维护数据完整性。同时需要注意唯一性约束不等于唯一索引。

非唯一索引

普通索引,用来辅助查询,不具备唯一性约束。

页分裂

索引按照顺序递增(不一定要连续,只要是递增就行),索引紧凑;随机插入为了保证索引的顺序行可能会造成索引的数据页分裂。数据页的分裂就产生数据空洞。分裂过程不仅影响性能还会影响空间使用率,因为原本一个数据页的数据,分成了两个数据页。 当然有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。

这里引用一张《Mysql 45讲》中的图

回表

非聚簇索引查询到主键,回到聚簇索引搜索的过程,我们称为回表。

覆盖索引

覆盖索引并不是一种索引类型,而是从非聚簇索引中就可以查询到的结果不用,再去回表。这种操作可以的好处是,非聚簇索引只存储索引值,并没有整行的数据,故其大小远小于聚簇索引,可以减少大量的io操作,所以我们常说不要用select *。

联合索引

联合索引是指对表上的多个列进行索引,联合索引也是一棵B+树,不同的是联合索引的键值数量不是1,而是大于等于2。 这里需要注意的是联合索引的结构 联合索引的所有索引列都出现在索引树上,并依次比较三列的大小进行排序。

作者水平有限,若有错误遗漏,请指出。

参考文章

1.MySQL索引背后的数据结构及算法原理

2.stackoverflow

3.深入理解数据库索引采用B树和B+树的原因

4.MySQL实战45讲

5.MySQL技术内幕 InnoDB存储引擎 第2版