MySQL索引结构原理分析

咱们在学习MySQL的时候常常会听到索引这个词,大概也知道这是什么,可是深究下去又说不出什么道道来。下面将会比较全面的介绍一下关于索引!html

1 索引是什么?

这里用百度百科的一句话来讲,在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。mysql

简单来讲,索引就是咱们一本书的目录,经过目录咱们才能更快在一本书中查找到咱们所要看的内容。一样的,经过索引咱们才能在数据库中查找到咱们的数据!git

2 没使用索引的MySQL

咱们知道索引能够加快咱们的查找,因此这里经过没有使用索引的查找能够更加地让咱们认识到使用索引的好处。github

咱们的MySQL基本的页存储结构是页,也就是咱们的数据记录都在页里面。算法

当咱们插入一条记录的时候就会存储在咱们的数据页中的存放行记录的位置,并在咱们的Page Directory页目录那里生成主键的信息。咱们的数据页中记录又能够组成一个单链表,每插入一条数据就会在尾节点那里添加上。sql

当咱们经过主键查找某条记录的时候能够在页目录中使用二分法快速定位到对应的槽,而后再遍历该槽对应分组中的记录便可快速找到指定的记录。若是不是主键的话,那么只能遍历单链表中的每条记录对比查找。数据库

因此,若是不用索引优化的话,那么在进行一条查找的sql的话,默认的流程是这样子的:服务器

  • 定位到记录所在的页(须要遍历双向链表,找到所在的页)
  • 从所在的页内中查找相应的记录(是否是根据主键查询,不是只能遍历所在页的单链表了)

若是在数据量特别大的时候,又是极端状况,遍历双向链表和单链表,速度就会显得很是慢!数据结构

3 B-Tree索引与B+Tree索引

3.1 B-Tree索引结构

当人们开始谈论索引的时候,若是没有特别指明类型的话,那么多半说的就是B-Tree(B树)因此,它使用的是B-Tree数据结构来存储数据,大多数的MySQL引擎都支持这种索引(但实际上不少存储引擎使用的是B+Tree,这个咱们稍后再谈到)。咱们这里经过B-Tree索引结构就能够极大的优化了上面的查找。性能

从图中咱们能够很明显的感觉到,使用索引后,就不须要再遍历双向链表那样去查找页,而是经过目录就能够很快的定位到咱们的实际的页。如查找id为1的数据

  • 首先小于4,能够肯定在p1下,指向磁盘页2
  • 在磁盘页里面,小于2,指向p1,而后查找到

并且,咱们也能够根据图总结出B-Tree的特色:

  • 全部键值数据分布在整棵树各个节点中
  • 咱们的查找有可能在非节点结束,好比上图中,咱们要找id为4的数据的话,在根节点就能够查找到
  • 全部叶子节点都在同一层,而且以升序排列

3.2 B+Tree索引结构

B+Tree索引是依据B-Tree索引基础上的一次优化,具体变化以下:

  • B+Tree 非叶子节点不存放数据

  • 叶子节点存储关键字和数据,非叶子节点的关键字也会沉到叶子节点,而且排序

  • 叶子节点两两指针相互链接,造成一个双向环形链表(符合磁盘的预读特性),顺序查询性能更高(区间查找更加方便)

咱们的B+Tree的优化到底有什么好处呢?

首先是咱们的数据只放在了叶子节点上面。这个惟一的好处就是咱们的非叶子节点能够存放更多的关键字了,总体就能够存放更多的数据。由于咱们MySQL查询过程是按页加载数据的,每加载一页就是一次IO操做,咱们根磁盘页存放的数据越少,关键字越多,那么总体的数据量就能够说是越多。

还有一个好处就是,在叶子节点造成双向环形链表。这样子若是要进行区间查询的话,只须要顺着叶子节点的指针向下查询就行。而若是是B-Tree的话,就须要返回上一级节点而后再读取磁盘页进行查找,节省了很多时间!

3.3 为何不采用别的树结构?

为何要采用B-Tree的结构,甚至是B+Tree的结构呢?

其实仍是跟咱们的磁盘读取的缘由有关。上面咱们说到了,MySQL查询过程是按页加载数据的。而咱们的操做系统通常将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。咱们的内存每次读取的就是MySQL分割成的一个页大小,也就是一个索引点,图中的一个磁盘页。

采用普通二叉树?不!

若是采用普通的二叉树的话,咱们要考虑到一种状况。那就是在极端的状况下,一棵树是会退化成链表的,那么树的优势就没有了。 这与咱们原来用双向链表有何异同?

采用红黑树?不!

那么可能有人说了,若是采用红黑树,树保持平衡不就好了吗?确实,红黑树等平衡树也能够用来实现索引,可是与咱们的B-Tree/B+Tree来讲性能要差不少。

咱们上面说到了内存每一次I/O都是载入一个索引节点,也就是一个磁盘页。若是数据不在同一个磁盘块上,那么一般须要移动制动手臂进行寻道,而制动手臂由于其物理结构致使了移动效率低下,从而增长磁盘数据读取时间。B-Tree/B+Tree相对于红黑树有更低的树高,进行寻道的次数与树高成正比,在同一个磁盘块上进行访问只须要很短的磁盘旋转时间,因此 B-Tree/B+Tree 树更适合磁盘数据的读取。

并且为了减小磁盘 I/O 操做,磁盘每每不是严格按需读取,而是每次都会预读。预读过程当中,磁盘进行顺序读取,顺序读取不须要进行磁盘寻道,而且只须要很短的磁盘旋转时间,速度会很是快。而且能够利用预读特性,相邻的节点也可以被预先载入。

4 哈希索引

MySQL除了B+树以外,还有一种常见的是就是哈希索引。

哈希索引就是采用必定的哈希算法,把键值换算成新的哈希值,检索时不须要相似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法便可马上定位到相应的位置,速度很是快

本质上就是把键值换算成新的哈希值,根据这个哈希值来定位

使用哈希索引最大的好处就是速度特别快,咱们只须要一次定位就能够找到咱们要的数据。时间复杂度为O(1),可是咱们的InnoDB(MySQL默认存储引擎)默认使用的倒是B+树索引,这也是由于哈希索引有必定的缺点:

  • 没法用于排序和分组
  • 只支持精确查找,没法用于部分查找和范围查找
  • 在有大量重复键值状况下,哈希索引的效率也是极低的---->哈希碰撞问题。
  • 不支持最左匹配原则

但是若是一个索引值被频繁使用的话,咱们的InnoDB会再B+Tree索引之上再建立一个哈希索引,用来方便快速查找。这个功能叫作“自适应哈希索引”。

5 聚簇索引与辅助索引

MySQL数据库中innodb存储引擎,B+树索引能够分为聚簇索引(也称汇集索引,clustered index)和辅助索引(有时也称非聚簇索引或二级索引,secondary index,non-clustered index)。这两种索引内部都是B+树,汇集索引的叶子节点存放着一整行的数据。

Innodb中的主键索引是一种聚簇索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、惟一索引。

5.1 聚簇索引

聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将汇集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。

Innodb经过主键汇集数据,若是没有定义主键,innodb会选择非空的惟一索引代替。若是没有这样的索引,innodb会隐式的定义一个主键来做为聚簇索引。

使用聚簇索引的优势:

  • 数据访问更快,由于聚簇索引将索引和数据保存在同一个B+树中,所以从聚簇索引中获取数据比非聚簇索引更快
  • 聚簇索引对于主键的排序查找和范围查找速度很是快

缺点:

  • 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,不然将会出现页分裂,严重影响性能。所以,对于InnoDB表,咱们通常都会定义一个自增的ID列为主键
  • 更新主键的代价很高,由于将会致使被更新的行移动。所以,对于InnoDB表,咱们通常定义主键为不可更新。
  • 二级索引(辅助)访问须要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

Innodb中聚簇索引示意图:

InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形

5.2 辅助索引

聚簇索引之上建立的索引称之为辅助索引,辅助索引访问数据老是须要二次查找。辅助索引叶子节点存储的再也不是行的物理位置,而是主键值。经过辅助索引首先找到的是主键值,再经过主键值找到数据行的数据页,再经过数据页中的Page Directory(页目录)找到数据行。

Innodb辅助索引的叶子节点并不包含行记录的所有数据,叶子节点除了包含键值外,还包含了相应行数据的聚簇索引键。辅助索引的存在不影响数据在聚簇索引中的组织,因此一张表能够有多个辅助索引。在innodb中有时也称辅助索引为二级索引。

Innodb中辅助索引示意图:

经过对比咱们就能够知道为何咱们对主键会有要求:

一、为何不建议使用过长的字段做为主键,由于全部辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。

二、为何用非单调的字段做为主键在InnoDB中不是个好主意,由于InnoDB数据文件自己是一颗B+Tree,非单调的主键会形成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段做为主键则是一个很好的选择。

固然,若是咱们经过索引优化,将辅助索引优化成覆盖索引,那么辅助索引也包含全部须要查询的字段的值。也就是咱们的索引就是咱们要的值,无需再访问主索引了。这里,涉及到索引的优化不过多介绍!

5.3 MyISAM实现对比

上面我介绍了在InnoDB存储引擎下的聚簇索引与辅助索引的实现,由于若是没有说明具体的数据库和存储引擎,默认指的是MySQL中的InnoDB存储引擎。可是咱们MySQL还支持MyISAM存储引擎,它也是支持聚簇索引与辅助索引的。

可是该引擎下的实现却有些不一样,咱们的聚簇索引和辅助索引没有什么区别,他们的叶子节点都不存放数据,而是存放数据记录的地址。惟一的区别就是聚簇索引要求key是惟一的,而辅助索引的key能够重复

因此若是严格的按照聚簇索引叶子节点存放数据来定义的话,MyISAM的索引都只能算是非聚簇索引!聚簇索引,或者严格说主键索引示意图:

辅助索引示意图:

为了更形象说明这两种存储引擎下两种索引的区别,咱们假想一个表以下图存储了4行数据。其中Id做为主索引,Name做为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差别。

5.4 索引优势及使用

咱们经过结构对比了使用索引的好处,总结下来的话就是:

  • 大大减小了服务器须要扫描的数据行数
  • 帮助服务器避免进行排序和分组,以及避免建立临时表(B+Tree 索引是有序的,能够用于 ORDER BY 和 GROUP BY 操做。临时表主要是在排序和分组过程当中建立,不须要排序和分组,也就不须要建立临时表)
  • 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一块儿)

可是咱们要知道,索引并非最好的解决方案。总的来讲,只有当索引帮助存储引擎快速找到记录带来的好处大于其带来的额外工做时,索引才是有效的。

  • 对于很是小的表、大部分状况下简单的全表扫描比创建索引更高效;
  • 对于中到大型的表,索引就很是有效;
  • 可是对于特大型的表,创建和维护索引的代价将会随之增加。这种状况下,须要用到一种技术能够直接区分出须要查询的一组数据,而不是一条记录一条记录地匹配,例如可使用分区技术(具体能够查看高性能MySQL第七章)。

6 总结

这里仅仅是介绍了索引结构原理等,关于索引还有不少,如全文索引,空间索引等,以及索引的优化之类,这更可能是咱们要去学习的。

7 参考资料

高性能MySQL(第三版)

MySQL存储结构

数据库两个神器索引和锁(修订版)

CS-Nodes聚簇索引与非聚簇索引

相关文章
相关标签/搜索