MySQL技术学习-索引

107 阅读11分钟

1. 什么是索引

1.1 索引的定义

索引就是一种数据结构,可以协助数据库高效的获取数据。从计算机的角度理解就是“排序的数据结构”,满足特定查找算法。这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现高级查找算法

1.2 索引的分类

根据物理存储的角度,可以将分为 聚簇索引 和 二级索引(辅助索引);

索引字段特性角度,可以分为主键索引、唯一索引、普通索引和前缀索引;

数据结构分类,又可分为 B+tree 索引、Hash 索引和 Full-text 索引。

2. 索引的优缺点

2.1 优点

  1. 索引可以快速定位数据所在位置,不需要进行全表扫描,提高了查询速度,降低了数据库IO成本
  2. 如果是唯一索引,可以保证数据库表中每行数据的唯一性,加快数据的检索速度。
  3. 索引可以加快表和表之间的连接,实现数据的参考完整性。即对于有依赖关系的子表和父表进行联合查询时可以提高查询效率。
  4. 在使用分组或排序子句进行数据查询时,可以减少查询中分组和排序的时间,降低CPU的消耗。

2.2 缺点

  1. 创建和维护索引需要时间成本:这是一个随着数据量增加而增大的成本。当数据量庞大时,为所有列创建和维护索引可能需要相当长的时间。
  2. 索引需要占用物理空间:每一个索引都会占据数据库的物理空间存储到磁盘上。随着数据量的增加,所需空间也会相应增大,尤其是当建立聚簇索引时,所需的空间将会更大。
  3. 索引会降低表的更新效率:每次对表中的数据进行增删改的操作时,索引都需要动态维护,这需要消耗一定时间,从而降低了数据维护的速度。

3. 索引数据结构的演变

3.1 二叉查找树

二叉查找树的特点:

二叉查找树的特点是一个节点的左子树的所有节点都小于这个节点,右子树的所有节点都大于这个节点

二叉查找树的缺点:

如果每次插入的数据都大于二叉查找树的数据,从而退化成了一条链表,查找数据的时间复杂度变成了O(n)

由于索引是存放在磁盘中的,所以每访问一个节点就会发生一次I/O操作,也就是说树的高度就是查询数据时磁盘 IO 操作的次数。所以二叉查找树不适合作为数据库的索引结构原因如下:

  1. 二叉查找树由于存在退化成链表的可能性,会使得查询操作的时间复杂度从 O(logn) 降低为 O(n)。
  2. 随着插入的元素越多,树的高度也变高,意味着需要磁盘 IO 操作的次数就越多,这样导致查询性能严重下降,再加上不能范围查询。

3.2 平衡二叉树(AVL树)

为了解决二叉查找树会在极端情况下退化成链表的问题,后面就有人提出平衡二叉查找树(AVL 树)

主要是在二叉查找树的基础上增加了一些条件约束:每个节点的左子树和右子树的高度差不能超过 1。也就是说节点的左子树和右子树仍然为平衡二叉树,这样查询操作的时间复杂度就会一直维持在 O(logn)

下图是每次插入的元素都是平衡二叉查找树中最大的元素,可以看到,它会维持自平衡:

平衡二叉查找树(AVL 树)平衡的过程

除了平衡二叉查找树,还有很多自平衡的二叉树,比如红黑树,它也是通过一些约束条件来达到自平衡,不过红黑树的约束条件比较复杂,不是本篇的重点重点,大家可以看《数据结构》相关的书籍来了解红黑树的约束条件。

下面是红黑树插入节点的过程,这左旋右旋的操作,就是为了自平衡。 f1.gif

不管平衡二叉查找树还是红黑树,都会随着插入的元素增多,而导致树的高度变高,这就意味着磁盘 I/O 操作次数多,会影响整体数据查询的效率。

3.3 B树

为了解决树的高度问题,引进了B 树。即不再像二叉树那样限制每个节点只能有2个子节点,它允许有M(M>2)个子节点,来解决树的高度问题,同时M就是B 树的阶。

M(M>2)阶的 B 树的特点:

1.  每个节点最多有 M-1个数据

2.  最多有 M个子节点

B 树的每个节点都包含数据(索引+记录),而用户的记录数据的大小很有可能远远超过了索引数据,这就需要花费更多的磁盘 I/O 操作次数来读到有用的索引数据。从而读取了很多无用的记录数据,而且为了读到有用的索引数据,要增加磁盘 I/O 操作次数。

另外,如果使用 B 树来做范围查询的话,需要使用中序遍历,这会涉及多个节点的磁盘 I/O 问题,从而导致整体速度下降。

3.4 B+树

B+树是对B树的优化,如下所示:

聚簇索引B+ 树

B+ 树的数据存储的特点:

  1. 聚簇索引的叶子节点才会存放实际数据(索引+记录),非叶子节点只会存放索引
  2. 非聚簇索引的叶子节点只会存放索引 + 主键值
  3. 所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表
  4. 非叶子节点的索引也会同时存在在子节点中,并且是在子节点中所有索引的最大(或最小)
  5. 非叶子节点中有多少个子节点,就有多少个索引

B+ 和 B 树的比较主要通过这三方面:单点查询范围查询插入和删除效率

单点查询

从单个索引查询来看的话,B 树的平均用时要比 B+ 树快一些。但是 B 树的波动比较大,因为 B 树每个节点都存放索引+记录,所以有时候在非叶子节点找到索引,有时候要到叶子节点才能找到索引。

B+ 树不同, 它的非叶子节点只会存放索引,所以相同数量的情况下,要比既存索引又存记录的 B 树来说,B+ 树非叶子节点能存放更多的索引,所以 B+ 树要比 B 树更矮胖,查询时磁盘I/O也会更少

范围查询

B 树和 B+ 树等值查询原理基本一致的,都是从根节点进行范围比较,然后递归进入子节点查询。但是,B+ 树的所有叶子节点有一个链表连接起来,这对范围查询非常有帮助。比如我们要找5和15的数据记录,我们可以先找5所在的子叶节点,然后使用链表找到15数据记录就可以了,不需要再从根节点查找15的数据记录,从而也会减少查询时间。

但是B 树只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。

插入和删除效率

B+ 树有大量的冗余节点,这样使得删除一个节点的时候,可以直接从叶子节点中删除,甚至可以不动非叶子节点,这样删除非常快,

比如下面这个动图是删除 B+ 树某个叶子节点节点的过程:

删除 B+ 树某个叶子节点节点的过程

注意,:B+ 树对于非叶子节点的子节点和索引的个数,定义方式可能会有不同,有的是说非叶子节点的子节点的个数为 M 阶,而索引的个数为 M-1(这个是维基百科里的定义),因此我本文关于 B+ 树的动图都是基于这个。但是我在前面介绍 B+ 树与 B+ 树的差异时,说的是「非叶子节点中有多少个子节点,就有多少个索引」,主要是 MySQL 用到的 B+ 树就是这个特性。

甚至,B+ 树在删除根节点的时候,由于存在冗余的节点,所以不会发生复杂的树的变形,比如下面这个动图是删除 B+ 树根节点的过程:

删除 B+ 树根节点的过程

B 树则不同,B 树没有冗余节点,删除节点的时候非常复杂,比如删除根节点中的数据,可能涉及复杂的树的变形,比如下面这个动图是删除 B 树根节点的过程:

删除 B 树根节点的过程

B+ 树的插入也是一样,有冗余节点,插入可能存在节点的分裂(如果节点饱和),但是最多只涉及树的一条路径。而且 B+ 树会自动平衡,不需要像更多复杂的算法,类似红黑树的旋转操作等。

因此,B+ 树的插入和删除效率更高。

总结:存在大量范围检索的场景,适合使用 B+树,比如数据库。而对于大量的单个索引查询的场景,可以考虑 B 树,比如 nosql 的MongoDB

4. MySQL 中的 B+ 树

MySQL 的存储方式根据存储引擎的不同而不同,我们最常用的就是 Innodb 存储引擎,它就是采用了 B+ 树作为了索引的数据结构。

image.png

但是 Innodb 使用的 B+ 树有一些特别的点:

  1. B+ 树的叶子节点之间是用「双向链表」进行连接,这样的好处是既能向右遍历,也能向左遍历
  2. B+ 树点节点内容是数据页,数据页里存放了用户的记录以及各种信息,每个数据页默认大小是 16 KB

Innodb 根据索引类型不同,分为聚集索引二级索引。他们区别在于,聚集索引的叶子节点存放的是实际数据,所有完整的用户记录都存放在聚集索引的叶子节点,而二级索引的叶子节点存放的是主键值,而不是实际数据。

因为表的数据都是存放在聚集索引的叶子节点里,所以 InnoDB 存储引擎一定会为表创建一个聚集索引,且由于数据在物理上只会保存一份,所以聚簇索引只能有一个,而二级索引可以创建多个。

扩展--红黑树

红黑树是一种自平衡二叉排序树,它属于平衡树,但是却没有平衡二叉树那么“平衡”。

红黑树的规则:

  • 规则1:每个节点不是黑色就是红色
  • 规则2:根节点为黑色
  • 规则3:红色节点的父节点和子节点不能为红色
  • 规则4:所有的叶子节点都是黑色(空节点视为叶子节点NIL)
  • 规则5:每个节点到叶子节点的每个路径黑色节点的个数都相等。

操作

  • 操作1:变色
  • 操作2:旋转

平衡二叉树和红黑树的区别

  • 平衡二叉树的左右子树的高度差绝对值不超过1,但是红黑树在某些时刻可能会超过1,只要符合红黑树的五个条件即可。
  • 二叉树只要不平衡就会进行旋转,而红黑树不符合规则时,有些情况只用改变颜色不用旋转,就能达到平衡。

总结

要设计一个 MySQL 的索引数据结构,不仅仅考虑数据结构增删改的时间复杂度,更重要的是要考虑磁盘 I/0 的操作次数。因为索引和记录都是存放在硬盘,硬盘是一个非常慢的存储设备,我们在查询数据的时候,最好能在尽可能少的磁盘 I/0 的操作次数内完成。

MySQL 默认的存储引擎 InnoDB 采用的是 B+ 作为索引的数据结构,原因有:

  • B+ 树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少。
  • B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;
  • B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。