1. 前言

对于常见的应用系统,读的流量远远高于写的流量,比如电商网站,商家在数据库中写入商品的价格和库存之后,访问页面的顾客会产生大部分的读流量。所以常见的现象是当应用系统的流量逐渐增加时,写操作不会成为数据库的性能瓶颈,但是复杂查询语句消耗的查询时间会越来越长,读操作更容易触碰数据库的查询性能瓶颈。MySQL 自身为了优化查询效率,更快的查询目标集合,定义了索引,也就是常用的 "键"(Key),MySQL 中的索引是单独存储在磁盘上的数据结构,使用索引可以快速查询满足特定条件的记录。

2. 谈一谈 InnoDB 存储引擎的索引数据结构

2.1 不同引擎的数据结构

面试官提问: MySQL 中 InnoDB 存储引擎底层的数据结构是什么?

题目解析:

以 MySQL 5.7 为例,首先查询官方文档,可以发现存储引擎和索引数据结构的对应关系,例如 InnoDB 对应 BTREE 索引,MEMORY 存储引擎对应哈希索引和 BTREE 索引,注意这里的 BTREE 实际指代的是 B+ 树,我们重点关注树的数据结构。

图片描述

存储引擎和索引类型的对应关系,表格来自 MySQL 5.7 官网

2.2 B 树和 B + 树

如果能正确回答 InnoDB 索引的底层数据结构是 B+ 树,面试官接下来可能会先考察候选人对数据结构本身的理解程度。

面试官:学过数据结构课程的同学,应该都听过 B 树和 B+ 树吧,这两种树有什么区别呢?

题目解析:我们尽量需要通过在白纸上画出 B 树和 B+ 树,画图的同时给面试官解释两种树的区别,需要从数据结构、优缺点方面分析(一般来说不需要深入到节点的插入和删除流程,因为比较复杂)
首先我们画出一个简化后的 B 树,如下图:

图片描述

B 树,图中绿色节点表示具体数据,蓝色节点表示指针,黄色表示键值,整个节点指代一个磁盘块

参考上图,我们定义一个 m 阶的 B 树的数据结构:

① 根结点至少有两个子节点;
② 除了根节点外,每个子节点都包含 n-1 个元素(数据)和 n 个子节点指针,其中 m/2 <= n <= m;
③ 所有的叶子结点都位于同一层;
④ 有序性:每个节点中的元素从小到大排列,节点当中 k-1 个元素正好是 k 个孩子包含的元素的值域分划。

画出 B 树只是为了衬托 B+ 树,B 树不会是面试的重点,接下来我们在白纸上画出一个典型的 B+ 树结构:

图片描述

B + 树,图中绿色节点表示具体数据,蓝色节点表示指针,黄色表示键值,整个节点指代一个磁盘块

对于一个 m 阶的 B+ 树,基本定义同 B 树相同:
① 除了根之外的每个节点都包含最少 m/2 个元素最多 m-1 个元素,对于任意的结点有最多 m 个子指针;
② 所有的叶子节点都在同一层;

除此之外,B+ 树相对于 B 树,需要特别区分的不同点有:
① 数据存储方式不同:B+ 树中间节点并不存储真正的数据,而是保存其叶子节点中最小值作为索引。例如上图中磁盘块 2 和磁盘块 3 中并没有黄色的 data 节点;
② 数据查找方式不同:每个叶子节点存在一个 next 指针,指向下一个叶子节点,形成了有序双向链表,从图中能明显看出来。所以 B 树只能由根节点往下二分查找,B+ 树除了这种查找方式,还支持在叶子节点中直接顺序遍历查找。

2.3 为什么使用 B + 树

在给面试官阐述清楚了 B 树和 B+ 树数据结构的区别之后,接下来面试官大概率会追根溯源,引出更深一步的问题:

** 面试官:** 你能说说为什么 MySQL 要选择 B+ 树作为索引的数据结构实现吗?

题目解析:

结合我们画出的数据结构图示,这个问题翻译过来其实是相对于 B 树,为什么要选择 B+ 树?
首先要分析数据库的读写瓶颈受限原因:计算机的存储是分层次的,CPU 里的 Cache 访问速度最快,速度更慢的是内存(容量小,断电情况下数据会丢失),读写最慢的是硬盘(容量大,数据可长期存储)。MySQL 的数据是持久化存储在硬盘中的,硬盘访问慢是因为:CPU 访问硬盘时会经过三个耗时步骤:

(1)寻道耗时:磁臂移动到磁道;
(2)旋转耗时:例如一个 7200 转的磁盘,表示每分钟能转 7200 次;
(3)数据传输耗时:从磁盘读出数据或者写入数据的过程。前两者都依赖于磁盘的机械运动,耗时非常久。所以磁盘 I/O 是一种非常昂贵的操作,数据库的读写操作需要经过尽可能少的磁盘 I/O。

我们在这个硬件基础上,我们依据 B 树的图示,模拟查询关键词 6 的一次查询过程:

(1)磁盘第一次 I/O 操作:找到磁盘块 1,读入内存;
(2)磁盘第二次 I/O 操作:比较关键词 6 在区间(0,7)之间,找到磁盘块 1 的左指针,根据指针找到磁盘块 2,读入内存;
(3) 磁盘第三次 I/O 操作:比较关键词 6 在区间(5,7)之间,找到磁盘块 2 的最右侧指针,根据指针找到磁盘块 6,读入内存;
(4) 寻找关键词:在磁盘块 6 中找到关键词 6,以及对应 data 数据。

可以发现,树的深度越深,查找需要的磁盘 I/O 次数就越多。现在我们再来分析 B + 树的优势:

(1) B + 树的磁盘 I/O 次数相对更少:利用 B 树 / B+ 树的有序性,从根节点每往子节点每查找一次,都要经过一次磁盘 I/O。相对 B 树,B+ 树的内部节点只包含下级指针,并不存放数据信息,所以对于同样大小的磁盘块,能够存储的记录个数更多(树的阶 m 更大),B+ 树的深度更低,所以磁盘 I/O 次数更少。
(2) B+ 树更适合范围查找:在进行范围查询时,B 树查询只能通过从根节点开始的递归查询,因为相邻节点在硬盘中不一定连续,缓存命中率差,而 B+ 树因为叶子节点形成有序链表,可以直接进行线性遍历。

综上,回答本题的核心点要抓住:
(1)明确磁盘 I/O 是数据库读写的硬件瓶颈。
(2)能够结合两种树不同的数据结构,分析得到 B+ 树的优势。

3. 小结

本章节介绍了 MySQL 中 InnoDB 存储引擎的底层 B+ 树数据结构,候选人需要区分 B 树和 B+ 树,以及从查询效率和硬件成本角度分析为什么在 MySQL 中会优选使用 B+ 树作为支撑。