摘要
本文主要讲述 B+ 树在 InnoDB 索引下的实现和产生的背景,不涉及 B+ 树节点的更新算法。
1. 前言
在上篇博文 MySQL的数据页结构和记录的管理 中,介绍了 InnoDB 数据页的内容,了解到数据页中的记录组成了一条有序链表,在页目录的辅助下可以使用二分法高效查询指定记录。页与页之间通过FIL_PAGE_PREV
和FIL_PAGE_NEXT
属性构成了双向链表。现在查询指定记录的过程如下,
- 找到记录所在的页。
- 通过二分法找到指定的记录。
第二步的查找方法我们已经很熟悉了,现在的关键问题是如何高效地找到记录所在的页,索引的故事由此展开。
2. 索引
2.1 简单的索引方案
每个页虽然通过双向链表有序排列,但是无法直接对链表使用二分法来优化搜索。根据 MySQL的数据页结构和记录的管理 中写到的,页中的行通过页目录结构组成数组,从而能使用二分搜索。那么自然也可以想到,通过一个目录将页转换为有序数组,还是以主键为例,结构如下图所示,
每个页都对应一个目录项,目录项存储了页号和页中记录的最小值,那么对这个目录项数组使用二分搜索就能快速找到记录所在的页。
2.2 InnoDB 的索引方案
简单的索引方案存在两个问题:
- 一页只有 16KB 记录,一个表如果比较大的话会存在大量的页,需要的目录项数组也会很大。我们知道数组需要一片连续地址空间进行存储,而连续的存储空间是非常珍贵的。
- 页中的数据可能被删除,页本身也会被删除,目录项数组中的元素也需要删除,我们知道数组删除一个元素的时间复杂度是
O(n)
,因为需要移动数组元素,覆盖被删除的元素。因此,目录项的维护需要很大的开销。
所以,InnoDB 使用树形结构对目录项进行管理,即用 B+ 树取代数组,结构如下图所示,
目录页和数据页所用记录是不同的,通过记录头中的record_type
属性进行区分,
- 0:普通的数据记录。
- 1:目录项记录。
- 2:Infimum 记录。
- 3:Supremum 记录。
目录页中的记录只有主键值,而数据页中的记录包含所有列的值,这也是为了节省空间,目录页只用于检索记录所在的页,而真正的数据在数据页中。即非叶子节点只保存主键列,真实的数据在叶子节点中。假设有如下 SQL,
select * from t where id = 183;
检索过程如下:
- 从根节点开始,用二分法找到
id=183
的记录在页号为 23 的页中。 - 在页 23 中使用二分法找到
id=183
的记录在页号为 13 的页中。 - 在页 13 中使用二分法找到
id=183
的记录。
InnoDB 中使用的 B+ 树与数据结构中的有所不同,但是更新节点的逻辑类似,有兴趣的可以自行了解,本文不涉及节点的更新。读者只需要知道 InnoDB 会维持 B+ 树,使得 B+ 树的层数不超过 4 层,每个目录页中的记录也是有个阈值的,如果超过这个阈值,目录页将会分裂为两个。
2.3 二级索引
现在我们知道根据主键怎么在索引树上进行搜索了,但是如果查询条件不是主键应该怎么办?
select * from t where col1 = 'a';
如果我们仍在主键构成的 B+ 树上进行搜索,那么只能逐行遍历,这个步骤就是著名的全表扫描。如果要使用索引,则需要对col1
列建立索引,这样的索引称为二级索引。与主键索引的区别在于,二级索引的数据页中的记录不会保存全部列,只会有索引列col1
和主键列id
。
上面的 SQL 是查询所有列,那么通过二级索引获得记录的主键后,需要再到主键索引中去搜索,以获得所有的列,这个过程称为回表。如果我们不想回表怎么办?可以修改SQL,
select col1 from t where col1 = 'a';
因为查询的列col1
在二级索引上就能获取,所以不需要回表操作,这样的索引也称为覆盖索引。
2.4 联合索引
索引的本质就是通过比较记录中指定列的大小,对记录进行排序,并将记录所在的页组织成 B+ 树的形式。联合索引就是同时对多个列进行排序,比如对col1, col2
建立联合索引,那么就是先比较col1
的大小,如果大小相同再比较col2
的大小。只要选定的列能够排序就能建立索引,但是一张表的索引不宜多建,毕竟需要额外的空间成本,以及维护 B+ 树的时间成本。
思考题
- 能够提高搜索速度的数据结构还有平衡二叉树和牛逼的红黑树,InnoDB 为什么使用 B+ 树建立索引?可以从磁盘 IO 方面考虑。
- 假设对
col1, col2
建立联合索引,下面哪个 SQL 能够使用到索引?为什么?select * from t where col1 = 'a' and col2 = 'b';
select * from t where col1 = 'a';
select * from t where col2 = 'b';