高性能Mysql 入门到放弃 之 B+-Tree (与B-Tree以及Binary Tree的对比解析)

2,557 阅读8分钟

最近开始看高性能Mysql 入门到放弃。对没错就是下面这本,放弃请输入 sudo rm -rf

问题由来:

索引:大家平常说的还有用的索引,如果没特别标明或者声明都是B-Tree索引,大多数Mysql引擎都支持这种索引,而Msyql常用引擎InnoDB等常为B+-Tree。

提出问题:

!!!注意是B树 B树 不是读作B减树 B-Tree,怎么读心里要有B数的。

WTF,B-Tree是什么Tree,是BinaryTree么?并不是那么什么是B+-Tree呢?有什么区别?我们平常用的二叉搜索树的时间复杂度不是Log(n)么,难道不够优秀么?

开始解决问题:

预备知识

磁盘IO

· 系统读取磁盘是将磁盘的基本单位---磁盘块(Block)读取出来。位一同一磁盘块的数据会被一次性读取出来。磁盘读取IO是机械动作,时间大概为内存读取的十万多倍。所以磁盘IO读写速度成为索引性能的主要指标。

1.复习一下二叉搜索树(Binay Search Tree,即BST)

如图,为二叉搜索树,它的时间复杂度为O(Log(n))。我们现在要执行搜索,那么考虑下最好的情况就是,搜索0009,也就是搜索的关键字为BST的根节点,读取一次IO;那么最坏的情况显而易见就是树最深的底层叶子节点(深度为N那么为N次磁盘IO)。

例如 :要索引0010,那么要进行四次磁盘IO操作,这已经比其他数据结构少很多次了。

能不能优化,怎么优化

· 如果优化要优化BST最坏的情况

· 最坏情况是由树的深度决定的,也就是说它是N阶BST,那么我们要压缩它

· 压缩 ?什么意思? 最近世界杯(以英格兰为例子),就是把BST(大竹竿克劳奇)变成B-Tree(小土豆鲁尼)

左为鲁尼,右边为克劳奇~

2 由之前的思考,引出了B-Tree:

B-Tree---平衡多路查找树

· B-Tree是为磁盘等外存储设备设计的一种平衡查找树。

· B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data],key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。(可以以python中的字典中的键值对理解,键唯一且不可改变,就是一个索引的标记,而内容存在data里面)

一棵m阶的B-Tree有如下特性:

每棵树都有好多属性概念,这里加粗的是我们用的,是对我们理解B-Tree有关的。

1. 每个节点最多有m个孩子。

  1. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
  2. 若根节点不是叶子节点,则至少有2个孩子
  3. 所有叶子节点都在同一层,且不包含其它关键字信息
  4. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
  5. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
  6. ki(i=1,…n)为关键字,且关键字升序排序。
  7. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

直接上图,这就是一个B-Tree,紫色为Key,黄色为data,蓝色为指针,仔细看发现会有磁盘块,结合之前讲的磁盘IO操作,可以梳理清楚B-Tree的索引流程

每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。

例子查找60

模拟查找关键字60的过程:

根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】 比较关键字60大于区间(17,35),找到磁盘块1的指针P3。 根据P3指针找到磁盘块4,读入内存。【磁盘I/O操作第2次】 比较关键字60小于在区间(65,87),找到磁盘块3的指针P1。 根据P2指针找到磁盘块9,读入内存。【磁盘I/O操作第3次】 在磁盘块9中的关键字列表中找到关键字60。 分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。

!!!:注意比之前BST多了在每一个磁盘页的索引比较,但是因为磁盘页已经被磁盘IO读取操作读入了内存中,所以内存IO操作比磁盘IO操作省时很多根本不是一个数量级的可以忽略不计,所以磁盘IO操作仍然是最重要的索引性能关键。

显而易见,这次我们进行了三次磁盘IO比之前的BST少了一次,试想一下在复杂的B-Tree索引中,会减少很多次磁盘IO操作,所以B-Tree更适合索引。

那么还可以再优化么?

iphone6/7/8 升级了就是 iphone6+/7+/8+ ,是的加个+(plus),看来都这么干。

B+-Tree:

B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。

之前我们记得B-Tree的索引和关键字key-data对存在磁盘里面,然后被磁盘IO操作读入内存,那么在磁盘页中,如果数据很大呢,如果是大数据呢!!!!

如果数据大的话,磁盘页无法装载,会使得一页的key的数量减少,还是会使得B-Tree的深度增加,这样还是会增加磁盘IO查询,计算机科学的老前辈就有一个大胆的想法,那么我把数据全部放在Tree的叶子节点呢。

在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。

B+Tree相对于B-Tree有几点不同:

非叶子节点只存储键值信息。 所有叶子节点之间都有一个链指针。 数据记录都存放在叶子节点中。

B+-Tree图

通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。

图中为聚集索引(clustered index),即范围查找,从跟节点开始。

!!!B-Tree与B+-Tree的区别!!!

· B+Tree的磁盘读写更低,因为非叶子节点可以存储更多的索引key,而key索引在同一层更集中,那么会降低磁盘IO读写次数。

· B+Tree的查询效率更稳定,由于最终指向文件内容的节点是叶子节点中关键字的索引,所以任何关键只查找都必须走从根节点到叶子节点的索引路径,路径是相似的(深度),所以更稳定(最好最坏情况都在最底层)。

· 区间访问性友好Mysql是关系型数据库,所以经常会按照区间来访问某个索引,B+树的叶子节点会按顺序建立起链状指针,加强区间访问性。

最后一个问题:

也是最重要的为什么Mysql会使用B/B+树来实现索引呢?

· Mysql是基于磁盘的数据库,索引是以索引文件的形式转存于磁盘中的

· 索引的过程就是要涉及磁盘IO的消耗,磁盘IO消耗比内存IO消耗要搞好几个数量级, 即索引涉及的在查找关键字时尽量减少磁盘IO消耗。

附加问题:

操作系统、数据库、或者说Mysql数据库引擎为什么使用B+-Tree的多呢?

· 因为B-Tree仍然未解决关键字的遍历,如果要遍历B-Tree上的所以关键字时间复杂度非常高,但是B+-Tree就不一样了,可以直接遍历叶子节点,支持范围索引,因为OS DB DB ENGINE 都经常使用范围索引,故 B+-Tree更胜一筹。

打完收工~