MySQL索引底层数据结构 B-Tree 与 B+Tree 的比较
一、B-Tree介绍
B-Tree是为磁盘等外存储设备设计的一种*平衡查找树①。因为在讲B-Tree之前先了解一下磁盘基础知识。系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么读取什么。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一个树节点拥有N组 key-value 键值对并且每个键值对还拥有一个指向子节点的指针和一个指向平级key的双向指针。key为记录的键值,反应在MySQL中就是索引列的值,value是一行表数据除去索引列的其他数据。对于不同的记录,key值互不相同。
一颗m阶的B-Tree有如下特性:
-
每个树节点最多有m组key-value 键值对。
-
除了根节点和叶子节点外,其他每个节点至少有m/2个子节点。
-
若根节点不是叶子节点,则至少有两个子节点。
-
所有叶子节点都在同一层,且不包含其他信息(比如指向其他叶子节点的指针)。
-
每个非终端节点包含n个关键字信息。
-
关键字的个数n满足:ceil(m/2)-1 <= n <= m-1 。
-
ki(i=1,…n)为关键字,且关键字升序排序。
-
Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1) 。
如下图所示为一个3阶的B-Tree:
每个节点分为若干个数据组,并且最大不超过3(不超过B-Tree的阶数)。每组数据占用一个盘块(data)的磁盘空间,一个升序排列的Key,一个指向子节点的指针P。
模拟查找字段值为29的过程:
-
根据根节点读取磁盘块1,这部分数据通常会预读到MySQL内存中并且长时间持有【第一次磁盘I/O】。
-
比较磁盘块1,根据二叉树的升序排列找到磁盘块3的指针P2【第二次磁盘I/O】。
-
同理找到磁盘块8,并且在里面找到所需数据29【第三次磁盘I/O】。
B-Tree分析:
- 好处:
- B-Tree极大的减少了磁盘的读取次数,能显著提高数据查询效率。
- 坏处:
-
每个节点都带有data数据,当查找的数据位于子节点时,磁盘I/O效率不高,并且因为data的存在,会导致构建索引时能存储的数据有限从而不不得不提高树的高度,这样会加大磁盘I/O开销。
-
范围查找时(id>1),需要重复上述步骤,效率比较低。
二、B+Tree介绍
B+Tree是在B-Tree的基础上做的优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是使用B+Tree实现其索引结构的。从上一节的B-Tree的分析讲述中可以看到B-Tree的局限性,而B+Tree对上述几点(包括但不限于)做了优化改进。
B+Tree相对于B-Tree有几点不同:
-
非叶子节点只存储键值信息。
-
所有叶子节点中都有双向指针,指向前后的数据。
-
数据只存放在叶子节点中。
如图所示:
B+Tree的节点查找原理(即MySQL Tree类索引的原理)与B-Tree相似,不同点在于B+Tree只有走到叶子节点才会返回数据。
三、B-Tree与B+Tree 对比
不难看出树的高度决定了MySQL在查询一条数据时所需要做的磁盘I/O次数。为了更明显的看出 B-Tree 和 B+Tree 的差异,下面做一个 m=3 阶时,B-Tree 和 B+Tree分别能容纳的索引数量。以InnoDB引擎为例,已知MySQL一次读取的数据大小为16KB②,可以通过 innodb_page_size 参数查看页的大小。已知树中一个指针的大小为6Byte③。为计算方便,我们假设key是大小为8Byte的Int型字段,value(图中的data)是大小为1KB表数据
一个节点的最大数据组存储量:Page/(Key+Value+P)
在B-Tree中,根据上述公式可以计算出一个节点的最大存储量为:16*1024/(8+1024+6) =15个当 m = 3 时,B-Tree能容纳的最大索引数为 15^3=3375个。
在B+Tree中,根据上述公式可以计算出一个非叶子节点的最大存储量为:161024/(8+6)=1170个,一个叶子节点的最大存储个数同 B-Tree一样为15个,当 m = 3 时,B+Tree能容纳的最大索引数为 11701170*15=20,533,500个。
由此可看看出B-Trre和B+Tree在同样高度的情况下,存储的索引数根本不在一个数量级上。
四、存在的问题
问题: B+Tree在增删改上是怎么操作的?
五、数据结构演示工具
数据结构动态演示: www.cs.usfca.edu/~galles/vis…
六、注释
[①] 二叉树的变种
[②] MySQL中的读取最小单位为 页(Page) ,16KB即为每 页(Page) 的默认大小
//查看当前页大小
mysql> show variables like 'innodb_page_size';
[③] 计算机中一个指针在一般情况下最大不会超过6Byte,这里我们取最大值