MySQL索引的数据结构-B+树介绍

1、树

树状图是一种数据结构 ,它是由n(n>=1)个有限结点组成一个具备层次关系的集合。把它叫作“树”是由于它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
它具备如下的特色:每一个结点有零个或多个子结点;没有父结点的结点称为根结点;每个非根结点有且只有一个父结点;除了根结点外,每一个子结点能够分为多个不相交的子树算法

205-MySQL索引的数据结构-B树介绍-01.png?x-oss-process=style/watermark

根结点:Asql

父节点:A是B,C的父节点数据库

叶子节点:D,E是叶子节点数据结构

树的深度/树的高度:高度为3测试

2、B+树

前面讲了索引的基本原理,数据库的复杂性,又讲了操做系统的相关知识,目的就是让你们了解,任何一种数据结构都不是凭空产生的,必定会有它的背景和使用场景,咱们如今总结一下,咱们须要这种数据结构可以作些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么咱们就想到若是一个高度可控的多路搜索树是否能知足需求呢?就这样,b+树应运而生(B+树是经过二叉查找树,再由平衡二叉树,B树演化而来)。优化

205-MySQL索引的数据结构-B树介绍-02.png?x-oss-process=style/watermark

2.1 B+树性质

  1. 索引字段要尽可能的小:经过上面的分析,咱们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每一个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N必定的状况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,若是数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为何每一个数据项,即索引字段要尽可能的小,好比int占4字节,要比bigint8字节少一半。这也是为何b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度降低,致使树增高。当数据项等于1时将会退化成线性表。
  2. 索引的最左匹配特性:当b+树的数据项是复合的数据结构,好比(name,age,sex)的时候,b+数是按照从左到右的顺序来创建搜索树的,好比当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来肯定下一步的所搜方向,若是name相同再依次比较age和sex,最后获得检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪一个节点,由于创建搜索树的时候name就是第一个比较因子,必需要先根据name来搜索才能知道下一步去哪里查询。好比当(张三,F)这样的数据来检索时,b+树能够用name来指定搜索方向,但下一个字段age的缺失,因此只能把名字等于张三的数据都找到,而后再匹配性别是F的数据了, 这个是很是重要的性质,即索引的最左匹配特性。

3、汇集索引和辅助索引

在数据库中,B+树的高度通常都在2~4层,这也就是说查找某一个键值的行记录时最多只须要2到4次IO,这倒不错。由于当前通常的机械硬盘每秒至少能够作100次IO,2~4次的IO意味着查询时间只须要0.02~0.04秒。spa

数据库中的B+树索引能够分为汇集索引(clustered index)和辅助索引(secondary index),操作系统

汇集索引与辅助索引相同的是:无论是汇集索引仍是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着全部的数据。设计

汇集索引与辅助索引不一样的是:叶子结点存放的是不是一整行的信息

3.1 汇集索引

InnoDB存储引擎表是索引组织表,即表中数据按照主键顺序存放。
而汇集索引(clustered index)就是按照每张表的主键构造一棵B+树,同时叶子结点存放的即为整张表的行记录数据,也将汇集索引的叶子结点称为数据页。
汇集索引的这个特性决定了索引组织表中数据也是索引的一部分。同B+树数据结构同样,每一个数据页都经过一个双向链表来进行连接。

若是未定义主键,MySQL取第一个惟一索引(unique)并且只含非空列(NOT NULL)做为主键,InnoDB使用它做为聚簇索引。

若是没有这样的列,InnoDB就本身产生一个这样的ID值,它有六个字节,并且是隐藏的,使其做为聚簇索引。

因为实际的数据页只能按照一棵B+树进行排序,所以每张表只能拥有一个汇集索引。
在多数状况下,查询优化器倾向于采用汇集索引。由于汇集索引可以在B+树索引的叶子节点上直接找到数据。
此外因为定义了数据的逻辑顺序,汇集索引可以特别快地访问针对范围值得查询。

205-MySQL索引的数据结构-B树介绍-13.png?x-oss-process=style/watermark

汇集索引的好处之一:它对主键的排序查找和范围查找速度很是快,叶子节点的数据就是用户所要查询的数据。如用户须要查找一张表,查询最后的10位用户信息,因为B+树索引是双向链表,因此用户能够快速找到最后一个数据页,并取出10条记录

# 参照第六小结测试索引的准备阶段来建立出表s1
mysql> desc s1; #最开始没有主键
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)

mysql> explain select * from s1 order by id desc limit 10; #Using filesort,须要二次排序
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2633472 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
row in set, 1 warning (0.11 sec)

mysql> alter table s1 add primary key(id); #添加主键
Query OK, 0 rows affected (13.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 order by id desc limit 10; #基于主键的汇集索引在建立完毕后就已经完成了排序,无需二次排序
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | s1    | NULL       | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
row in set, 1 warning (0.04 sec)

汇集索引的好处之二:范围查询(range query),即若是要查找主键某一范围内的数据,经过叶子节点的上层中间节点就能够获得页的范围,以后直接读取数据页便可

mysql> alter table s1 drop primary key;
Query OK, 2699998 rows affected (24.23 sec)
Records: 2699998  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
rows in set (0.12 sec)

mysql> explain select * from s1 where id > 1 and id < 1000000; # 没有汇集索引,预估须要检索的rows数以下
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2690100 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.00 sec)

mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 where id > 1 and id < 1000000; # 有汇集索引,预估须要检索的rows数以下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 1343355 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
row in set, 1 warning (0.09 sec)

3.2 辅助索引

表中除了汇集索引外其余索引都是辅助索引(Secondary Index,也称为非汇集索引),与汇集索引的区别是:辅助索引的叶子节点不包含行记录的所有数据。

叶子节点除了包含键值之外,每一个叶子节点中的索引行中还包含一个书签(bookmark)。该书签用来告诉InnoDB存储引擎去哪里能够找到与索引相对应的行数据。

因为InnoDB存储引擎是索引组织表,所以InnoDB存储引擎的辅助索引的书签就是相应行数据的汇集索引键。以下图

205-MySQL索引的数据结构-B树介绍-14.png?x-oss-process=style/watermark

辅助索引的存在并不影响数据在汇集索引中的组织,所以每张表上能够有多个辅助索引,但只能有一个汇集索引。当经过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并经过叶子级别的指针得到只想主键索引的主键,而后再经过主键索引来找到一个完整的行记录。

举例来讲,若是在一棵高度为3的辅助索引树种查找数据,那须要对这个辅助索引树遍历3次找到指定主键,若是汇集索引树的高度一样为3,那么还须要对汇集索引树进行3次查找,最终找到一个完整的行数据所在的页,所以一共须要6次逻辑IO访问才能获得最终的一个数据页。

205-MySQL索引的数据结构-B树介绍-15.png?x-oss-process=style/watermark

3.3 汇集索引和非汇集索引的区别

汇集索引

  1. 纪录的索引顺序与无力顺序相同
    所以更适合between and和order by操做
  2. 叶子结点直接对应数据
    从中间级的索引页的索引行直接对应数据页
  3. 每张表只能建立一个汇集索引

非汇集索引

  1. 索引顺序和物理顺序无关
  2. 叶子结点不直接指向数据页
  3. 每张表能够有多个非汇集索引,须要更多磁盘和内容
  4. 多个索引会影响insert和update的速度

4、再看B+树

B+树和二叉树、平衡二叉树同样,都是经典的数据结构。B+树由B树和索引顺序访问方法(ISAM,是否是很熟悉?对,这也是MyISAM引擎最初参考的数据结构)演化而来,可是在实际使用过程当中几乎已经没有使用B树的状况了。

B+树的定义十分复杂,所以只简要地介绍B+树:B+树是为磁盘或其余直接存取辅助设备而设计的一种平衡查找树,在B+树中,全部记录节点都是按键值的大小顺序存放在同一层的叶节点中,各叶节点指针进行链接。

咱们先来看一个B+树,其高度为2,每页可存放4条记录,扇出(fan out)为5。

205-MySQL索引的数据结构-B树介绍-03.png?x-oss-process=style/watermark

能够看出,全部记录都在叶节点中,而且是顺序存放的,若是咱们从最左边的叶节点开始顺序遍历,能够获得全部键值的顺序排序:五、十、1五、20、2五、30、50、5五、60、6五、7五、80、8五、90。

4.1 B+树的插入操做

B+树的插入必须保证插入后叶节点中的记录依然排序,同时须要考虑插入B+树的三种状况,每种状况均可能会致使不一样的插入算法,如表5-1所示。

205-MySQL索引的数据结构-B树介绍-04.png?x-oss-process=style/watermark

咱们用实例来分析B+树的插入,咱们插入28这个键值,发现当前Leaf Page和Index Page都没有满,咱们直接插入就能够了。

205-MySQL索引的数据结构-B树介绍-05.png?x-oss-process=style/watermark

此次咱们再插入一条70这个键值,这时原先的Leaf Page已经满了,可是Index Page尚未满,符合表5-1的第二种状况,这时插入Leaf Page后的状况为50、5五、60、6五、70。咱们根据中间的值60拆分叶节点。

205-MySQL索引的数据结构-B树介绍-06.png?x-oss-process=style/watermark

由于图片显示的关系,此次我没有能在各叶节点加上双向链表指针。最后咱们来插入记录95,这时符合表5-1讨论的第三种状况,即Leaf Page和Index Page都满了,这时须要作两次拆分。

205-MySQL索引的数据结构-B树介绍-07.png?x-oss-process=style/watermark

能够看到,无论怎么变化,B+树老是会保持平衡。可是为了保持平衡,对于新插入的键值可能须要作大量的拆分页(split)操做,而B+树主要用于磁盘,所以页的拆分意味着磁盘的操做,应该在可能的状况下尽可能减小页的拆分。所以,B+树提供了旋转(rotation)的功能。

旋转发生在Leaf Page已经满了、可是其左右兄弟节点没有满的状况下。(旋转是为了减小拆分页,若是叶子节点的左右兄弟节点还有空位置,那就旋转一下就能把当前数据插入了;若是本身和左右兄弟位置都满了那再怎么旋转也出不来位置了,只能拆分页了)这时B+树并不会急于去作拆分页的操做,而是将记录移到所在页的兄弟节点上。一般状况下,左兄弟被首先检查用来作旋转操做,这时咱们插入键值70,其实B+树并不会急于去拆分叶节点,而是作旋转,50,55,55旋转。

205-MySQL索引的数据结构-B树介绍-08.png?x-oss-process=style/watermark

能够看到,采用旋转操做使B+树减小了一次页的拆分操做,而这时B+树的高度依然仍是2。

4.2 B+树的删除操做

B+树使用填充因子(fill factor)来控制树的删除变化,50%是填充因子可设的最小值。B+树的删除操做一样必须保证删除后叶节点中的记录依然排序,同插入同样,B+树的删除操做一样须要考虑如表5-2所示的三种状况,与插入不一样的是,删除根据填充因子的变化来衡量。

205-MySQL索引的数据结构-B树介绍-09.png?x-oss-process=style/watermark

首先,删除键值为70的这条记录,该记录符合表5-2讨论的第一种状况,删除后。

205-MySQL索引的数据结构-B树介绍-10.png?x-oss-process=style/watermark

接着咱们删除键值为25的记录,这也是表5-2讨论的第一种状况,可是该值仍是Index Page中的值,所以在删除Leaf Page中25的值后,还应将25的右兄弟节点的28更新到Page Index中,最后可获得图。

205-MySQL索引的数据结构-B树介绍-11.png?x-oss-process=style/watermark

最后咱们来看删除键值为60的状况,删除Leaf Page中键值为60的记录后,填充因子小于50%,这时须要作合并操做,一样,在删除Index Page中相关记录后须要作Index Page的合并操做,最后获得图。

205-MySQL索引的数据结构-B树介绍-12.png?x-oss-process=style/watermark

相关文章
相关标签/搜索