一次真实的美团面试经历

那么,我们来看一次笔者的一次真实美团面试,面试官关于Mysql的数据结构的提问。

面试官:

Mysql的底层数据结构有了解过么?

帅航:

有,是B+树。

面试官:

为什么是B+树呢,刚才说到HashMap用的哈希表,红黑树查询效率都挺高的,为什么Mysql不用呢?

帅航:

哈希表的一次查询是很快,但是范围查询就很搓了;至于红黑树的话,由于二叉树的特性,数据量太大的情况下,树会很高,由于数据是存储在磁盘上的,这样与磁盘的IO会很频繁,是不可以接受的。所以选择B+树。

面试官:

那树高的问题B树也可以解决,为什么非要是B+树。

帅航:

因为B+树非叶子节点是不存储数据的,B树是存储数据的,而且B+树的叶子结点上有所有的数据,而B树的数据是散落在树的各个层级里面,且,B+树的子节点还有指针相连,是一个双向链表、这样,虽然B树的节点数比B+树少,但是应付一些全表扫描的场景上,B+树更有优势;并且,在范围查询的场景中,我们找到了一个值,B+树会更方便顺序取出剩下的所有值。

面试官:

Mysql中的B+树大概会有几层,查询的时间是怎么样的?

帅航:

B+树的高度在数据库中大概只有2到4层,磁盘的IO1秒至少完成100次,所以查询时间应该在0.02s到0.04s。

为什么是B+树

要理解这个问题,我们先要理解折半查找算法。对于查找算法来讲,效率最高的就大概是折半查找算法,有序数组的元素查找,包括跳表的原理都是这种类似的二分查找算法。

这种时间复杂度为O(log2n)的查找算法是很恐怖的,比如我们经常完的查数字的游戏,1到5000000的数字,我们第一次猜2500000,就可以排除一半的数据。要知道 2^23 = 8388608,也就是说,我们最多猜23次就可以找到你心里想的1到500万中的一个数字,这种效率真的是很恐怖。

那么再说一下二叉查找树,二叉查找树首先是一个二叉树。每个节点可以有左右两个子节点。那么二叉查找树就是树上面的任意一个节点,只要存在于左子树的上面的节点,就一定是小于这个节点的;只要是存在于右子树上面的节点,就一定是大于这个节点的。

Mysql的存储结构 - B+树_结点

这种性质的二叉树,就可以使用类似于折半算法,从根节点找起,如果寻找的节点大于根节点的值就去找右子树,如果寻找的节点小于根节点的值就去找左子树。

那么这样有没有什么问题呢?

问题就是,如果我这棵树是瘸腿的,就会近似的退化成一个链表。

Mysql的存储结构 - B+树_数据_02

而这种的查找又只能从头开始遍历。所以为了不让树“瘸腿”,有了二叉平衡树和红黑树,这两种树都会让整棵树维持一种“平衡”,不会出现这种大“瘸腿”的现象,从而保证了查找的效率。

那么这种数据结构在内存中是没有什么问题的,但是在磁盘中就会有问题了,我们都知道磁盘没有内存那么快,这种频繁的查找IO肯定是不行的,那么就引出了B树(B树就是B-树)。

B树有如下特性:

是一种多路搜索树(并不是二叉的):

1.定义任意非叶子结点最多只有M个儿子,且M>2;

2.根结点的儿子数为[2, M];

3.除根结点以外的非叶子结点的儿子数为[M/2, M];

4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)

5.非叶子结点的关键字个数=指向儿子的指针个数-1;

6.非叶子结点的关键字:K[1], K[2], …, K[M-1],且K[i] < K[i+1];

7.非叶子结点的指针:P[1], P[2], …, P[M],其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;

8.所有叶子结点位于同一层;

如:(M=3)

Mysql的存储结构 - B+树_结点_03

这样就降低了树的高度,也就降低了磁盘的IO次数。但是很遗憾,数据库采用的还不是B树,而是B+树。

B+树的特性:

B+树是B-树的变体,也是一种多路搜索树:

1.其定义基本与B-树同,除了:

2.非叶子结点的子树指针与关键字个数相同;

3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树

4.B-树是开区间;

5.为所有叶子结点增加一个链指针;

6.所有关键字都在叶子结点出现;

如:(M=3)

Mysql的存储结构 - B+树_数据_04

mysql就是使用这种B+树来存储数据的,就像面试中笔者的回答,所有的数据都在叶子节点,方便做全文检索,且,每次IO的次数都是一样的,比较稳定。