本文首发于公众号:托尼学长,立个写 1024 篇原创技术面试文章的flag,欢迎过来视察监督~
在最近的技术面试中,这个问题被问到很多,但同学们往往给出的答案不太准确。
回答好这个问题,我们需要具备一些逆向思维,用排除法来证明其他数据结构做索引的缺点,也就证明了B+ Tree是最适合当数据库索引的。
所以,我们就先来盘一盘,使用Hash、Binary Tree、Red Black Tree、B Tree或Skip List作为数据库索引,到底会有哪些问题。
Hash(哈希)
MySQL InnoDB是支持手动创建Hash索引的,也具备自适应Hash索引的特性,只不过还是以B+ Tree索引为主。
自适应Hash索引,是MySQL InnoDB为一些热点查询自动创建并管理的,旨在以O(1) 的时间复杂度完成查询操作。
但Hash索引只擅长于等值查询的场景,对其他场景的支持并不好,且存在Hash冲突的风险,不如B+ Tree均衡全面。
| 特性 | Hash索引 | B+ Tree索引 |
|---|---|---|
| 等值查询 | O(1) | O(log n) |
| 范围查询 | 不支持 | 高效支持 |
| 排序分组 | 需额外排序 | 天然有序 |
| 组合索引 | 不支持最左前缀 | 支持最左前缀 |
| 冲突风险 | 严重时影响性能 | 不存在 |
Binary Tree(二叉树)
Binary Tree并不适合做索引,原因在于每个节点至多拥有两个子节点,数据多的话会导致树高急剧增加,IO代价太大。
甚至在极端情况下,插入的数据是有序的(递增或递减),二叉树会退化为链表,导致查询性能从 O(logn) 退化为 O(n)。
Red Black Tree(红黑树)
Red Black Tree是一种自平衡的二叉搜索树(Balanced Binary Search Tree),通过特定的规则和颜色标记(红/黑)来维护树的平衡性,确保在最坏情况下基本操作(插入、删除、查找)的时间复杂度为 O(log n)。
因此,Red Black Tree所面临的问题和Binary Tree一样,在数据多的话会导致树高急剧增加,IO代价太大,并不适合做数据库索引。
B Tree(B树)B+ Tree索引是B Tree索引的一种变体,两者存在如下两点主要区别:
(1)B Tree索引是“全数据节点”模式,在非叶子结点和叶子结点上都存储键值和数据,其中非叶子结点还会存储子节点的指针。
B+ Tree索引是“叶子结点数据”模式,只在叶子结点会存储键值和数据,非叶子结点只存储键值和子节点的指针。
(2)B+ Tree索引的叶子节点会通过指针连接形成链表。
两者直观比较如下图所示:
B Tree索引
B+ Tree索引
两者对比如下,从中我们可以看到,B+ Tree索引有一些B Tree索引所不具备的优势。
| 特性 | B Tree索引 | B+ Tree索引 |
|---|---|---|
| 查询效率 | 树高可能略高,查询效率较低 | 树高更低,查询效率更高 |
| 范围查询 | 效率低,需遍历多个节点 | 效率高,通过叶子节点的链表顺序访问 |
| 空间利用率 | 较低,非叶子节点存储键值、数据、子节点指针 | 较高,非叶子节点仅存储键值、子节点指针 |
Skip List(跳表)
Skip List一种可以进行二分查找的有序链表,在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。
我们都知道,Redis ZSet的底层数据结构,就是通过Skip List来实现对于Score的范围查询。
从上图中可以看到,B+ Tree和Skip List都是通过自上而下的查询方式提升性能的,且在最下面的一层包含了所有的数据,其有序性可更好地支持范围查询。
两者对比如下,从中我们可以看到,B+ Tree索引具备更多优势。
| 特性 | Skip List索引 | B+ Tree索引 |
|---|---|---|
| 查询效率 | 树高很高,查询效率低 | 树高更低,查询效率更高 |
| 新增操作 | 根据随机函数确定层数,性能高 | 有旋转和维持树平衡的开销,性能低 |
| 空间开销 | 较高(多层指针冗余) | 较低 |
| 事务支持 | 实现复杂,一致性难保证 | 天然适配 MVCC 和 ACID |
最后再解释一下两种数据结构的查询效率。
B+ Tree是扇出很高的多叉树结构,每个结点是一个16k的数据Page,按照每行数据1k大小进行计算,三层树高可存放2000万行数据,读一次数据只需要进行三次磁盘IO操作。
而Skip List则是一种链表结构,一个节点存储一行数据,若同样存储2000万数据的话,Skip List二分查找需要2的24次方,也就是读一次数据需要24次磁盘IO操作。
而一般情况下,我们的业务系统都属于读多写少型的,读写比例大概是9:1,很难接受Skip List这么多的IO次数。
结语
因此,从多种索引结构综合来看,B+ Tree虽然不是在各个方面最优秀的,却是各个方面没有短板的,所以主流数据库都选择它作为主要索引。