MySQL索引的原理,B+树、汇集索引和二级索引的结构分析

  索引是一种用于快速查询行的数据结构,就像一本书的目录就是一个索引,若是想在一本书中找到某个主题,通常会先找到对应页码。在mysql中,存储引擎用相似的方法使用索引,先在索引中找到对应值,而后根据匹配的索引记录找到对应的行。html

  咱们首先了解一下索引的几种类型和索引的结构。mysql

索引类型

B树

  大多数存储引擎都支持B树索引。b树一般意味着全部的值都是按顺序存储的,而且每个叶子也到根的距离相同。B树索引可以加快访问数据的速度,由于存储引擎再也不须要进行全表扫描来获取数据。下图就是一颗简单的B数。sql

 

 

B树的查询流程:
如上图我要从找到E字母,查找流程以下:
(1)获取根节点的关键字进行比较,当前根节点关键字为M,E<M(26个字母顺序),因此往找到指向左边的子节点(二分法规则,左小右大,左边放小于当前节点值的子节点、右边放大于当前节点值的子节点);
(2)拿到关键字D和G,D<E<G 因此直接找到D和G中间的节点;
(3)拿到E和F,由于E=E 因此直接返回关键字和指针信息(若是树结构里面没有包含所要查找的节点则返回null);
(4)经过指针信息取出这条记录的全部信息;数据结构

 

B+树

下图为B+树的结构,B+树是B树的升级版,咱们能够观察一下,B树和B+树的区别是什么?性能

B+树和B树的区别是:优化

1. B树的节点中没有重复元素,B+树有。spa

2. B树的中间节点会存储数据指针信息,而B+树只有叶子节点才存储。指针

3. B+树的每一个叶子节点有一个指针指向下一个节点,把全部的叶子节点串在了一块儿。code

 

从下图咱们能够直观的看到B树和B+树的区别:紫红色的箭头是指向被索引的数据的指针,大红色的箭头即指向下一个叶子节点的指针。htm

咱们假设被索引的列是主键,如今查找主键为5的记录,模拟一下查找的过程:

B树,在倒数第二层的节点中找到5后,能够马上拿到指针获取行数据,查找中止。

B+树,在倒数第二层的节点中找到5后,因为中间节点不存有指针信息,则继续往下查找,在叶子节点中找到5,拿到指针获取行数据,查找中止。

 

B+树每一个父节点的元素都会出如今子节点中,是子节点的最大(或最小)元素。叶子节点存储了被索引列的全部的数据。

那B+树比起B树有什么优势呢?

1. 因为中间节点不存指针,一样大小的磁盘页能够容纳更多的节点元素,树的高度就小。(数据量相同的状况下,B+树比B树更加“矮胖”),查找起来就更快。
2. B+树每次查找都必须到叶子节点才能获取数据,而B树不必定,B树能够在非叶子节点上获取数据。所以B+树查找的时间更稳定。
3. B+树的每个叶子节点都有指向下一个叶子节点的指针,方便范围查询和全表查询:只须要从第一个叶子节点开始顺着指针一直扫描下去便可,而B树则要对树作中序遍历

 

了解了B+树的结构以后,咱们对一张具体的表作分析:

create table Student(
    last_name varchar(50) not null, 
    first_name varchar(50) not null, 
    birthday date not null, 
    gender int(2) not null, 
    key(last_name, first_name, birthday)
);

对于表中的每一行数据,索引中包含了name,birthday列的值。下图显示了该索引的结构:

索引对多个值进行排序的依据是create table语句中定义索引时列的顺序,即若是名字相同,则根据生日来排序。

 

B+树的结构决定了这种索引对如下类型的查询有效:

全值匹配
和索引中全部的列进行匹配,例如查找姓名为Cuba Allen,生日为1960-01-01的人。


匹配最左前缀
查找姓为Allen的人,即只用索引的第一列。


匹配列前缀
匹配某一列的值的开头部分,例如查找全部以J开头的姓的人。


匹配范围值
查找姓在Allen和Barrymore之间的人。


精确匹配某一列并范围匹配另一列
查找姓为Allen,名字是字母K开头的人。即第一列last_name全匹配,第二列first_name范围匹配。


只访问索引的查询
查询只须要访问索引,无需访问数据行。这种索引叫作覆盖索引。

 

一些限制:

  • 若是不是按照索引的最左列开始查找,没法使用索引。例如上面例子中的索引没法用于查找某个特定生日的人,由于生日不是最左数据列。也不能查找last_name以某个字母结尾的人。
  • 不能跳过索引的列。上述索引没法用于查找last_name为Smith而且某个特定生日的人。若是不指定first_name,则mysql只能使用索引的第一列。
  • 若是查询中有某个列的范围查询,则右边全部的列都没法使用索引优化查找。例如查询WHERE last_name=’Smith’ AND first_name LIKE ‘J%’ AND birthday=‘1996-05-19’,这个查询只能使用索引的前两列。

 

哈希索引

  哈希索引,只有精确匹配索引全部列的查询才有效。对于每一行数据,存储引擎都会对全部的索引列计算一个哈希码。哈希索引将全部的哈希码存储在索引中,同时在哈希表中保存指向每一个数据行的指针。若是多个列的哈希值相同,索引会以链表的方式存放多个指针记录到同一个哈希条目中。
由于索引自身只存储对应的哈希值,因此索引的结构十分紧凑,哈希索引查找的速度很是快。可是哈希索引也有它的限制:

  • 哈希索引不是按照索引顺序存储的,没法用于排序。
  • 不支持部分索引列匹配查找。
  • 不支持范围查找。

 

汇集索引(clusterd index)

  每一个存储引擎为InnoDB的表都有一个特殊的索引,叫汇集索引。汇集索引并非一种单独的索引类型,而是一种数据存储方式。当表有汇集索引的时候,它的数据行实际上存放在叶子页中。一个表不可能有两个地方存放数据,因此一个表只能有一个汇集索引。
  由于是存储引擎负责实现索引,所以不是全部的存储引擎都支持汇集索引。InnoDB表中汇集索引的索引列就是主键,因此汇集索引也叫主键索引。
例以下面这张InnoDB表:

create table Student(
    id int(11) primary key auto_increment,
    last_name varchar(50) not null, 
    first_name varchar(50) not null, 
    birthday date not null
);

汇集索引(主键索引)的结构以下图:

这是一课B+树,它的叶子页包含了行的所有数据,节点页只包含了索引列(即主键)。

 

二级索引(secondary indexes)

  对于InnoDB表,在非主键列的其余列上建的索引就是二级索引(由于汇集索引只有一个)。二级索引能够有0个,1个或者多个。二级索引和汇集索引的区别是什么呢?二级索引的节点页和汇集索引同样,只存被索引列的值,而二级索引的叶子页除了索引列值,还存这一列对应的主键值。

 

InnoDB和MyISAM的数据分布对比

如下表为例,咱们看下InnoDB和MyISAM是如何存储这个表的:

create table layout_test(
    col1 int(11) primary key, 
    col2 int(11) not null, 
    key(col2)
);

 

InnoDB表的数据分布

汇集索引(主键索引)分布以下:

能够看到,叶子节点存储了整个表的数据,而不是只有索引列,每一个叶子节点包含了主键值、事务ID、用于事务和MVCC的回滚指针以及全部的剩余列(col2)。

 

二级索引分布以下:

二级索引的叶子节点中存储的不是“行指针”,而是主键值,并以此做为指向行的“指针”。这样的策略减小了当出现行移动或者数据页分裂时二级索引的维护工做。使用主键当作指针会让二级索引占更多空间,但好处是InnoDB在移动行时无需更新二级索引中的这个指针。

 

MyISAM表的数据分布

col1列上的索引:

col2列上的索引:

实际上MyISAM中主键索引和其余索引在结构上没有什么不一样。

 

从下图能够看出InnoDB和MyISAM保存数据和索引的区别。

 

汇集索引的优势:

  • 能够把相关数据保存在一块儿,例如实现电子邮箱时,根据用户ID来汇集数据,读取少数的数据页就能获取某个用户的所有邮件。
  • 汇集索引将索引和数据保存在同一个B树中,所以从汇集索引中获取数据比在非汇集索引中要快一些。

汇集索引的缺点:

  • 插入速度严重依赖插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。假如磁盘中的某一个已经存满了,可是新增的行要插入到这一页当中,存储引擎就会把该也分裂成两个页面来容纳该行,这就是一次页分裂操做。页分裂会致使表占用更多的磁盘空间。
  • 更新汇集索引列的代价很高,会强制InnoDB将每一个被更新的行移动到新的位置。
  • 用二级索引访问数据须要两个索引查找,不是一次。由于要先从二级索引的叶子节点得到主键值,再根据这主键去汇集索引中查到对应的行,因此须要两次B树查找。

 

顺序主键的策略:

  在InnoDB表中使用自增主键是既简单性能又高的策略,这样能够保证数据按顺序写入。最好避免随机的汇集索引,从性能的角度考虑,使用UUID来做为汇集索引是很糟糕的,这样不只插入行花费的时间长,并且索引占用的空间也更大。

 

参考资料:

《高性能mysql(第三版)》

https://stackoverflow.com/questions/870218/differences-between-b-trees-and-b-trees

https://dev.mysql.com/doc/refman/8.0/en/innodb-index-types.html

https://www.jianshu.com/p/1f2560f0e87f