索引是一种数据结构,主要用来在大量数据中快速定位需要查找的数据。索引最形象的比喻就是图书的目录了,如果你想要在书中快速找到一篇文章,最常用的方法应该就是查找目录,而不是从书的第一页逐页的寻找吧。
可以实现索引效果的数据结构有很多,如哈希表,有序数组和搜索树。使用这三种数据结构进行索引实现,都各有自己的优点和不足,这里不多赘述。在mysql的InnoDB存储引擎中,索引的实现采用的是搜索树的方式。
而搜索树有多个变种,实现方式也各不相同,在mysql中使用的是B+树。为了更好的学习B+树,我们可以先了解下二叉查找树,平衡二叉树和 B 树这三种数据结构,因为B+ 树就是从他们三个逐步演化而来的。
索引的实现原理
为了方面下文描述,建立如下表结构:
CREATE TABLE `user_info` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(11) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB;
二叉查找树
学习二叉查找树,可以参考如下这张图:
从图中可以看到,使用字段id,为数据表建立了一个二叉查找树的索引。在二叉查找树的节点中存储了键(key)和数据(data)。键对应表中的 id,数据对应表中的行数据。
二叉查找树的特点就是任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。顶端的节点我们称为根节点,没有子节点的节点我们称之为叶节点。
如果我们需要查找 id=12 的用户信息,利用我们创建的二叉查找树索引,查找流程如下:
- 将根节点作为当前节点,把 12 与当前节点的键值 10 比较,12 大于 10,接下来我们把当前节点>的右子节点作为当前节点。
- 继续把 12 和当前节点的键值 13 比较,发现 12 小于 13,把当前节点的左子节点作为当前节点。
- 把 12 和当前节点的键值 12 对比,12 等于 12,满足条件,我们从当前节点中取出 data,即 id=12,name=xm。
利用二叉查找树我们只需要 3 次即可找到匹配的数据。如果在表中一条条的查找的话,我们需要 6 次才能找到。
平衡二叉树
利用二叉查找树可以快速定位到需要的数据,主要是因为树的高度足够低。通过上面的查找过程可以发现,定位一个数据的过程中,最多访问"树高"个节点。二叉查找树的高度决定了查找效率。
如果一个二叉查找树形似下图:
那么这个二叉查找树的查询效率就会退化成线性表,用这种索引查找数据,无异于全表扫描。
平衡树,主要就是为了保证二叉查找树中,每个节点的左右子树的高度尽可能相同,来让二叉查找树的左右子树看起来尽可能平衡,最终让整个二叉树的高度足够低。
平衡二叉树保证了树的构造是平衡的,当我们插入或删除数据导致不满足平衡二叉树不平衡时,平衡二叉树会进行调整树上的节点来保持平衡。具体的调整方式这里就不介绍了。
平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
B树
我们都知道数据保存在内存中是不安全的,因为内存具有易失性,机器重启后,内存中的数据就会丢失,常用的做法是将数据保存到磁盘等外围存储设备中,保存到磁盘可以提高数据的安全性,但是,磁盘的访问效率相比内存来说,是很慢的。
在使用磁盘存储索引和数据时,为了提高数据的查询效率,应该尽量少的减少磁盘访问次数。另外,从磁盘中读取数据时,都是按照磁盘块来读取的,并不是一条一条的读。为了减低访问磁盘的次数,可以在每个搜索树的节点上存储更多的数据。这样,访问一次磁盘查找到的节点上就会有更多的数据。除此之外,如果用二叉树存储海量的数据,树的高度就会变得很高。粗略计算了下,存放10亿个节点的二叉树,树高大概是33,也就是需要访问33次磁盘。如果搜索数是N叉的话,假设N=1000,那么存放10亿数据,树高只有3。可见节点存放的数据量越多,理论上树高越小。
B树的数据结构如下图所示:
从上图可以看出,B 树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子节点,子节点的个数一般称为阶,上述图中的 B 树为 3 阶 B 树,高度也会很低。
假如我们要查找 id=28 的用户信息,那么我们在上图 B 树中查找的流程如下:
- 先找到根节点也就是页 1,判断 28 在键值 17 和 35 之间,那么我们根据页 1 中的指针 p2 找到页 3。
- 将 28 和页 3 中的键值相比较,28 在 26 和 30 之间,我们根据页 3 中的指针 p2 找到页 8。
- 将 28 和页 8 中的键值相比较,发现有匹配的键值 28,键值 28 对应的用户信息为(28,bv)。
B+树
B+ 树是对 B 树的在节点存储内容,以及节点之间的关系上做了优化。B+树的数据结构如下:
在B树中,每个节点除了存放key,还会存放这个key对应的数据data,data数据的存在,导致每个节点可以存放的数据变少。
而在B+ 树中,非叶子节点上是不存储数据的,仅存储键值,之所以这么做是因为在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。
如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。因为数据都存放在叶子结点上,所以使用B+树进行数据查询,都需要访问树高个节点,B+树数据访问效率也会很稳定。
因为 B+ 树索所有数据均存储在叶子节点,而且数据是按照顺序排列的。那么 B+ 树使得范围查找,排序查找变得异常简单。而 B 树因为数据分散在各个节点,要实现这一点是很不容易的。
mysql中的索引
在mysql中,索引主要分为聚簇索引和非聚簇索引。两者的差异主要就是叶子结点中存放的内容不同,聚簇索引是用主键作为B+中非叶子结点的key,用行数据作为叶子节点存放的是数据。而非聚簇索引是用指定的字段作为非叶子结点的key,用主键作为叶子节点存放的数据。
聚簇索引
聚簇索引是非聚簇索引的基础,所以每个数据表都会有一颗主键索引树。
聚簇索引的B+树结构如下:
由上图可以看出,聚簇索引中的叶子节点中包含了完整的数据内容。
非聚簇索引相关的内容,主要包括唯一索引,联合索引,覆盖索引,联合索引最左原则,索引下推等内容。限于篇幅的原因,放在下一篇文章:一文搞懂mysql中的索引(下)。
注:本文内容部分引用 MySQL索引-B+树。