MySQL索引

102 阅读8分钟

一、什么是索引,它的作用是什么?

最通俗的类比,索引可以类比为一本书的目录。目录上标注了主体内容属于哪一页,当我们查阅书籍某一章节时,只需要对目录进行一个检索,就能很快找到需要查阅的内容在书的哪一页。数据库索引也是同理,本质也就是帮助数据库从海量数据中快速定位数据的存放地址。

二、索引的选择可以是哪几种常见数据结构?

数据库的一个主要作用是查询。因此可以快速想到几个常用的数据结构:

  • hash表

    哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。这种方式的优点是查询速度极快,缺点是范围检索很慢,需要遍历全部数据。

  • 数组

    假如将索引按照值递增的方式进行存储于数组中。此时查找可以使用二分查找,速度很快,也支持范围查询,只需要找到检索范围的起点位置,接着往后遍历到不满足即可。针对查询的需求是满足的,但数据库还需要支持插入的需求,当插入数据时,数组需要进行移位,此时会消耗大量性能。

  • 二叉树

    二叉树是基于链表的结构,因此可以很轻松的满足插入的需求,也可以利用二叉搜索树的特性,快速的找到数据的位置。看起来查询和插入的需求都能满足,只需要付出一点点代价,那就是每次插入都需要维持为一颗平衡二叉树,保证查询性能的稳定性。基于数据结构而言,二叉树确实是很好的选择,但数据库的存储物理特性,导致了不能选择二叉树,这个特性便是数据需要存储到磁盘上。二叉树的每一个节点,在磁盘上对应的是一个数据块,每读取一个数据块都是一次I/O操作,当数据量比较大时,二叉树的高度会很高,因此会有很多次I/O操作。

三、为什么选择B+树做为底层数据结构?

二叉树的树形结构具有“高瘦”的特性,从数据结构的角度而言是可以作为底层数据结构的。因此,找到一种“矮胖”的树形结构便可以满足数据库在逻辑与物理二方面的要求。为此,有以下B树和B+树可以选择,它们都是“N叉树”。

  • B树

    以下是一颗3阶B树的简易数据结构图:

    .png

    每个节点都会存在关键词,子树的数量等于关键词的数量+1,因此当关键词越多时,阶数越大,节点越少,就形成了一种矮胖的结构,这种结构可以减少磁盘中数据块的数量,因此可以减少I/O操作。 假如我要查找key=415的数据,从图中经过3次比对后,能找到一个大致的区间,然后在区间内进行遍历即可,这种结构在等值查找时是很快的,但针对于范围查找是乏力的。 想要确定一个范围,必须要对树进行中序遍历,才能拿到连续的值,这个遍历是比较消耗性能的。

    B树还存在一个问题,在于每个节点是可以存储数据的,因此对于一个查询,性能是不稳定的,因为可能很快,也可能较慢。而且节点可以存储数据,所以相对而言,同样空间大小的节点能存储的索引数量变少了,可以简单理解为能存放的关键词变少了,这样树的阶数会受到影响。

  • B+树

    以下是一颗3阶B+树的建议数据结构图:

    .png

    和B树不同的是,子树的数量和关键词的数量一致,并且子节点会继承父节点中用于拆分子树的关键词,非叶子节点内部不会存储数据,因此所有的数据和关键词最终都会抵达叶子节点。叶子节点内部的记录是一个单向链表,同级节点间是一个双向链表。

    B+树的优点在于非叶子节点不存储数据,因此能存储的索引会更多,树的阶数会更低,其次查询较为稳定,每次查询都会抵达叶子节点。而且针对范围查询在2个节点上有双向链表可供快速检索,同一节点上有页目录可供快速检索,约等于二分查找 因此MySQL的索引选择的结构是B+树。

四、索引存储了哪些数据?是如何提高搜索效率的?

假如我一个表中的字段存在2个索引,分别是id(主键索引),key(普通索引)。因此会存在2颗B+树。二棵树的结构如下:

1.png

当使用key索引进行检索时,获取到的是主键索引的值,然后在通过主键索引的B树,获取到对应行的值。索引分为聚簇索引(主键索引)和非聚簇索引,聚簇索引所在B+树存储的数据就是行数据,非聚簇索引存储的数据是聚簇索引。

因此,主键索引应该尽可能的占用更少的空间。当主键索引占用较大时,非聚簇索引B+树存放的数据就会相对更少。因此数据页会更多,阶数可能因此变高。

还有一点需要注意,主键索引尽量自增。很简单,假如要插入一个id=800的数据,直接插入就可以了,但要插入一个id=350的数据,此时在R3所在数据页内部会新增一条数据,如果当前数据页占满,此时需要申请一个新的数据页,并且移动部分数据到新页中,这个过程叫页分裂,不仅会影响性能,也会影响空间利用率。相反删除的时候也会存在页合并的情况。

前面说过,当使用非聚簇索引查询时,查询的结果是主键索引,然后又会通过主键索引查询,这个过程叫回表。那么有没有办法避免回表呢?其实非聚簇索引能查询到的结果是主键索引和当前非聚簇索引的并集,在非聚簇索引所在B+树中,自身的值是用于比较的关键词,因此是可以获得的。如果这个并集就已经包含了所有需要查询的字段,那么就不会进行回表操作,如果没有包含完整,则会进行回表。 因此可以根据具体情况创建联合索引作为非聚簇索引查询,最好的情况是直接使用主键索引查询。

五、如何选择普通索引和唯一索引

建立索引的目的是为了效率。更细分则是查找数据的效率和更新数据的效率。

  • 查找

    唯一索引的查找是匹配到第一个数据时直接返回,普通索引的查找是第一个匹配项作为开始,然后一直到不匹配项,中间所有的匹配项都是返回结果。而MySQL将数据从磁盘读入内存,不是一行一行读取的,而是一个数据页一次性读取,因此在查找性能上几乎没有差距。

  • 更新

    当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB 会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

    change buffer中的操作应用到原数据页,得到最新结果的过程称为merge。除了访问这个数据页会触发merge外,系统有后台线程会定期merge。在数据库正常关闭的过程中,也会执行merge操作。因此使用这种方式会明显减少I/O操作。

    这种方式只能用于普通索引中,因为唯一索引需要判断是否重复,因此需要将数据读入内存。

那么是使用普通索引更好吗?其实不尽然。假如你在更新数据后又会立刻进行查询,则会立刻执行merge操作,这样不仅不会减少I/O操作,还会增加change buffer的维护成本。因此这种方式只适用于更新多,查询少的表。