小面试官教你 MySQL——引擎、索引和算法

MySQL 引擎、索引和算法

弄懂了 MySQL 的基本 CURD 操做以后,下一个必须掌握的知识就是 MySQL 的索引。html

我在面试中,常常喜欢针对 MySQL 的知识由浅入深地问下去,了解候选人对 MySQL 知识的了解到了哪个层级。上一篇文章中的那些知识太基础了,我是不会拿来问的。所以我会问的第一个问题必然是 MySQL 的索引。mysql

关于 MySQL 的索引,我大体会问下面几个问题:git

  1. 你知道 InnoDB 索引所使用的算法是什么吗?
  2. 为何 InnoDB 要使用 B+ 树而不是其余的数据结构呢?
  3. 在 InnoDB 中,是否是必需要有主键?若是建表的时候不指定主键会怎样?
  4. InnoDB 的主键和索引有什么区别?

要回答这两个问题,咱们须要了解下面几个知识:引擎、索引、树程序员

MySQL 索引的背景知识

MySQL 的引擎

MySQL 在设计之初,就容许嵌入不一样的引擎。数据库的核心算法其实是由引擎来实现的。早期 MySQL 数据库有如下三个主流引擎:面试

  1. MyISAM: 这是 MySQL 5.5 以前的默认引擎。因为其不支持事务处理,所以在新的系统中基本上没什么人用了。
  2. InnoDB: 这是 MySQL 5.6 以及以后的默认引擎。若是你不知道应该选什么引擎的话,选它基本没错。
  3. Memory: 这是一个特殊的引擎,该引擎存取的数据,所有放在内存中,不会落入磁盘。所以当数据库宕机或重启后,数据就会丢失。自从 Redis 兴起以后,memory 引擎也式微了。

因为新系统中几乎都选用了 InnoDB 引擎,所以下文中如无特别说明,则指的均为 InnoDB 引擎下的软件原理和行为。算法

存储系统中的 “页”

按照参考资料1 InnoDB 引擎的关键特性包括如下内容:sql

  1. 插入缓冲(Insert Buffer)
  2. 两次写(Double Write)
  3. 自适应哈希索引(Adapitve Hash Index)
  4. 异步 IO(Async IO)
  5. 刷新临接页(Flush Neighbor Page)

能够看到五个特性中,有四个特性是和存储直接相关的。学过计算机组成原理的话就会知道,计算机存储,根据其与 CPU 的距离由近到远有如下几个:数据库

  1. 寄存器
  2. 缓存
  3. 内存
  4. 硬盘

其中寄存器对于程序员来讲通常是不须要关注的;缓存没法直接在程序中影响和操做。对于绝大部分的计算机程序所操做的存储为内存和硬盘。操做系统读取内存和硬盘的时候,基本上以 “页” 为单位进行操做的。segmentfault

为何须要以 “页” 为单位操做呢?我们先看内存:内存实际上是彻底能够随机读取的,也就是说 CPU 若是想要读取哪个地址上的数据,那么一条指令就能够取到。可是应用程序上存取的不是实际内存,而是虚拟内存。而操做系统映射虚拟内存,只能以页为单位进行映射。所以,即使你操做的是内存,仍是请自觉尽可能对齐页。api

重点则是硬盘。硬盘包括两种类型,一种是磁盘,也就是以磁性元件来存储数据的介质;另外一种是所谓的 SSD,也就是固态硬盘。关于磁盘的读取原理和过程,我以前写过两篇文章《高性能磁盘 I/O 开发学习笔记 -- 硬件原理篇》和《高性能磁盘 I/O 开发学习笔记 -- 软件手段篇》。简单而言,因为硬件原理的限制,硬盘的读写有如下两个特色:

  1. 慢: 磁盘须要转才能转到对应的位置;SSD 好不少,但也比不上内存,毕竟要从长长的总线加载到内存中呢
  2. 块: 在软件中使用的 page,在硬件届常常是能够对应到 block。磁盘和 SSD 的数据修改都是以 block 为单位的

索引的原理

MySQL 定位的是大量数据的数据存储。每个表中存储的数据目标是百万行起跳;数据数据结构较为简单,索引效率高的话,千万也没有问题。实际使用中,也有上亿的场景。这就像一个图书馆,咱们须要对每一本书进行标记和索引,这样在查找书目(数据)时,才可以高效地查询到所须要的数据。

索引的原理,本质上就是解决快速查找和快速修改的目的。其次则是解决很是纠结的硬盘写入流程,整个过程当中还须要各类防止崩溃和宕机——毕竟 MySQL 的数据一致性要求是很高的。

做为 MySQL,常常须要关注的数据结构有如下几个:哈希表、B-树、B+树。

哈希表

哈希(hash)算法相信你们都了解了,本文就不赘述。哈希算法的时间复杂度为 O(1)。在 MySQL 中,前文提到的三个主要引擎只有 Memory 引擎在索引中使用了哈希算法。那为何其余引擎不是用这个算法呢?由于其余引擎须要考虑落地硬盘的问题啊。
哈希的算法虽然简单,可是哈希表在实际应用中须要考虑表的扩容和缩容的问题。当哈希表须要扩容/缩容的时候,整个表中的全部元素均可能须要从新排列。Memory 引擎是不落磁盘的,不 care。但即使是 Memory,也不适合存储大量数据。实际上在现实使用中,Memory 的使用场景已经不断被压缩,大部分已经被 Redis 所取代了。

B树

B树的原理其实相对而言比较简单,它就是一棵树。B树相比起最基本的树结构来讲,比较特别的就是树的分裂和合并。主要就是在数据库的内容增长和减小的时候所发生。具体的过程读者能够查阅网上相关的资料,很是多。
B树的特色是:

  • 每个节点能够多路分叉,不是二叉树。查询效率上比起二叉树而言确定是较弱的。
  • 在其每个节点上都会存储数据

不是 MySQL 的 MongoDB 使用的就是 B树。那么这里的问题是:为何采用 B树,而不是搜索效率更高的红黑树呢?(面试考点注意!)

  1. 首先,B树每个节点是有必定长度的,在引擎的设计中,会充分利用这一特性,结合前文所提到的 “页”,将同一个节点放在同一页中,大大提升硬盘的存取效率
  2. 其次,首先,红黑树在插入的过程当中,常常会出现节点的旋转,旋转次数多了以后,可能致使附近的节点分散分布在硬盘的多个页中。那么在数据落地的时候,就会大大下降效率,而且提升失败的风险

须要注意的是,B树有时候也被称为B-树,可是有些文章中B树指的又不是B-树,而是二叉树(Binary Tree)。读者在识别这些用词的时候,要结合上下文区分。

B+树

B+树是本文的重点,由于 InnoDB 使用的树结构就是B+树。一个B+树的示意结构图以下:

看起来和B树是很像的,可是实际上有两个很是关键的差别:

  1. B树的数据不只存在叶子结点中,分支节点也存储。可是B+树的数据仅仅存储在叶子结点中,分支节点仅保存索引。若是要查询到数据,那么必须查到叶子结点才能查到。
  2. B树的各个节点之间除了父子关系以外,不会有其余的关系。可是B+树的叶子节点之间,还有双向链表相互链接。这一点的好处是,对于设计遍历操做,或者是 offset - limit 的操做,可以大大地提升搜索效率

InnoDB 索引的分类

前文提到,InnoDB 所使用的算法是B+树;B+树上的非叶子结点存储的只是数据结构的索引,用于定位子结点用的,而不是 “数据库索引”。

那么问题来了:InnoDB B+ 树的叶子结点保存的是什么呢?这就引出了第一个分类:

按存储内容区分

Clustered Index,中文翻译不一,有 “聚簇索引”、“汇集索引”、“聚类索引” 等。聚簇索引指的是在叶子结点上,存储的数据就是完整的 MySQL 的一行数据。

那么在B+树的内部,用什么来索引叶子结点呢?答案是主键(main key)。在实际应用中,很大一部分的表在建立的时候都会把第一列定义为 int 或者 bigint 类型,而且指定为 auto increment类型并设定为主键。这是一个很是通用并且很是保险的作法。咱们联系一下前文 B+ 树的特性就能够发现,若是针对这个自增ID直接进行查询、或者是以自增ID为条件进行大于、小于等范围操做,都很是高效。

那么若是在建表的时候不明确指定自增ID的话,会怎样呢?B+树失效?
对于 MyISAM 引擎来讲,主键不是必须的,若是不指定主键,那就没有主键。可是在 InnoDB 中主键是必要的,若是不指定主键的话,那么 InnoDB 会隐含地添加一个 24 位宽的整型ID做为主键。但这会致使这个整型 ID 不可见,致使相关的一些操做好比 last inserted id 变得没有意义。所以在实际操做中咱们仍是须要显式地指定主键。

对于 InnoDB 来讲,聚簇索引能够等同于就是主键的索引。

Secondary Index,中文翻译也不一,有 “非聚簇索引”、“辅助索引”、“二级索引” 等。在非聚簇索引的叶子结点上,存储的是对应的那一行 MySQL 数据的主键。

若是经过非聚簇索引,也就是除了主键之外的字段查找到了条目以后,此时 InnoDB 仅仅拿到了两个数据:一个是当前节点的索引列的值;另外一个是主键。若是客户端还请求了其余数据的话,那么 InnoDB 须要再到当前表的聚簇索引中进行查阅。这个动做称为 “回表” 查询。

按组成逻辑区分

按照组成逻辑区分的话,InnoDB 索引能够分为:

  • 主键索引: 这就是前文提到的聚簇索引
  • 单列索引: 除了主键以外的非聚簇索引的最简单的模式
  • 联合索引: 顾名思义,就是多列的索引
  • 惟一索引: 这是单列索引和联合索引的特例,不一样的就是在整个表中仅在符合单列或者多列条件所指定的同一个/一组值的数据行,仅容许存在一条

在这里须要特别说明的是联合索引。笔者以前一直觉得联合索引就是索引了一个字段以后,在获得的结果中再对下一个字段进行索引。但后来查阅资料以后才知道其实并非。

当建立一个联合索引时,索引中的每个字段的值,都会在索引的数据结构中出现。这里我以为这篇文章讲得已经很是准确和简要了,读者能够直接参阅。

覆盖索引

“覆盖索引” 并非一种索引的类别,而是一种查询状况。前文提到过,在大部分按照索引进行的查询时,还须要进行回表查询从而获得客户端所须要的其余字段。可是前文也提到,若是你查询的字段,当前的索引已经彻底覆盖了,那么这个时候 InnoDB 不会再进行多余的回表查询,而是在非聚簇索引查询中就直接把字段返回了。这个现象就称为 “覆盖索引”(covering index)。

空间索引

InnoDB 在 5.7.4 labs 版本中开始支持对空间索引的支持。简单而言,咱们平时的索引就是一个纬度的,好比一个数字x。而空间索引则是对一个空间坐标系的索引,好比 (x, y) 或者是 (x, y, z)。

InnoDB 的索引采用 R 树,读者感兴趣的话能够参阅相关资料进一步学习。在大部分的应用场景中,若是不涉及地理数据的话,空间索引咱们用得仍是比较少的。

回答面试题

好了,前面的面试题,咱们就能够大体地回答出来了:

问: InnoDB 索引所使用的算法是什么?
答: B+树
问: 为何 InnoDB 要使用 B+ 树而不是其余的数据结构呢?
答: 相比起红黑树,B树的节点以页为单位,而页则与硬盘中的页相互绑定,所以能够优化硬盘存取的效率
相比起红黑树,B树的深度比较稳定,查找的耗时比较可预期——这个实际上是B树的分裂和旋转策略所决定的,读者能够进一步阅读资料了解
相比起B树,B+树的叶子结点之间包含双向链表,能够极大地优化遍历类和 offset - limit 类查询的耗时
InnoDB 在使用 B+树中,使用了非聚簇索引,这一算法能够极大地减小索引所占的空间,从而大大减小索引占用的内存和硬盘空间,提升索引重建效率
其实这个答案不惟一,读者若是感兴趣还能够进一步阅读参考资料
问: 在 InnoDB 中,是否是必需要有主键?若是建表的时候不指定主键会怎样?
答: 前文已经回答了:主键是必须有的,若是不指定的话,InnoDB 会自动建立一个6字节的自增ID
问: InnoDB 的主键和索引有什么区别?
答: InnoDB 的主键是一种特殊的索引,也就是聚簇索引;而其余的索引都是非聚簇索引。区别就是聚簇索引上保存的是完整的一行数据,而非聚簇索引上保存的是索引值以及主键

参考资料


本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

原做者: amc,欢迎转载,但请注明出处。

原文标题:小面试官教你 MySQL——引擎、索引和算法

发布日期:2020-11-09

原文连接:https://cloud.tencent.com/developer/article/1336510,也是本人的博客

相关文章
相关标签/搜索