InnoDB的B+索引

摘要

本文主要讲述 B+ 树在 InnoDB 索引下的实现和产生的背景,不涉及 B+ 树节点的更新算法。

1. 前言

在上篇博文 MySQL的数据页结构和记录的管理 中,介绍了 InnoDB 数据页的内容,了解到数据页中的记录组成了一条有序链表,在页目录的辅助下可以使用二分法高效查询指定记录。页与页之间通过FIL_PAGE_PREVFIL_PAGE_NEXT属性构成了双向链表。现在查询指定记录的过程如下,

  1. 找到记录所在的页。
  2. 通过二分法找到指定的记录。

第二步的查找方法我们已经很熟悉了,现在的关键问题是如何高效地找到记录所在的页,索引的故事由此展开。

2. 索引

2.1 简单的索引方案

每个页虽然通过双向链表有序排列,但是无法直接对链表使用二分法来优化搜索。根据 MySQL的数据页结构和记录的管理 中写到的,页中的行通过页目录结构组成数组,从而能使用二分搜索。那么自然也可以想到,通过一个目录将页转换为有序数组,还是以主键为例,结构如下图所示,
页的目录数组结构
每个页都对应一个目录项,目录项存储了页号和页中记录的最小值,那么对这个目录项数组使用二分搜索就能快速找到记录所在的页。

2.2 InnoDB 的索引方案

简单的索引方案存在两个问题:

  • 一页只有 16KB 记录,一个表如果比较大的话会存在大量的页,需要的目录项数组也会很大。我们知道数组需要一片连续地址空间进行存储,而连续的存储空间是非常珍贵的。
  • 页中的数据可能被删除,页本身也会被删除,目录项数组中的元素也需要删除,我们知道数组删除一个元素的时间复杂度是O(n),因为需要移动数组元素,覆盖被删除的元素。因此,目录项的维护需要很大的开销。

所以,InnoDB 使用树形结构对目录项进行管理,即用 B+ 树取代数组,结构如下图所示,
InnoDB中的B+树索引
目录页和数据页所用记录是不同的,通过记录头中的record_type属性进行区分,

  • 0:普通的数据记录。
  • 1:目录项记录。
  • 2:Infimum 记录。
  • 3:Supremum 记录。

目录页中的记录只有主键值,而数据页中的记录包含所有列的值,这也是为了节省空间,目录页只用于检索记录所在的页,而真正的数据在数据页中。即非叶子节点只保存主键列,真实的数据在叶子节点中。假设有如下 SQL,

select * from t where id = 183;

检索过程如下:

  1. 从根节点开始,用二分法找到id=183的记录在页号为 23 的页中。
  2. 在页 23 中使用二分法找到id=183的记录在页号为 13 的页中。
  3. 在页 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+ 树的时间成本。

思考题

  1. 能够提高搜索速度的数据结构还有平衡二叉树和牛逼的红黑树,InnoDB 为什么使用 B+ 树建立索引?可以从磁盘 IO 方面考虑。
  2. 假设对col1, col2建立联合索引,下面哪个 SQL 能够使用到索引?为什么?
    • select * from t where col1 = 'a' and col2 = 'b';
    • select * from t where col1 = 'a';
    • select * from t where col2 = 'b';
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值