一步步分析为何B+树适合做为索引的结构 以及索引原理 (阿里面试)

mysql的B+树索引 查找使用了二分查找,redis 跳表也使用了二分查找法,kafka查询消息日志也使用了二分查找法,二分查找法时间复杂度O(logn);html

参考:redis的索引底层的 跳表原理 实现 聊聊Mysql索引和redis跳表 ---redis的跳表原理 时间复杂度O(logn)(阿里)mysql

参考:kafka如何实现高并发存储-如何找到一条须要消费的数据(阿里)redis

参考:二分查找法:各类排序算法的时间复杂度和空间复杂度(阿里)算法

在MySQL中,主要有四种类型的索引,分别为:B-Tree索引,Hash索引,Fulltext索引(MyISAM 表)和R-Tree索引,本文讲的是B-Tree索引。sql

后面的索引原理必定要看,过重要了,阿里两我的都问这个mysql的索引原理数据库

mysql使用了 B+索引:数组

B树:有序数组+平衡多叉树; 
B+树:有序数组链表+平衡多叉树;
缓存

1、Mysql索引主要有两种结构:B+Tree索引和Hash索引 

(a) Inodb存储引擎 默认是 B+Tree索引数据结构

(b) MyISAM 存储引擎 默认是Fulltext索引;并发

(c)Memory 存储引擎 默认 Hash索引;

Hash索引

mysql中,只有Memory(Memory表只存在内存中,断电会消失,适用于临时表)存储引擎显示支持Hash索引,是Memory表的默认索引类型,尽管Memory表也可使用B+Tree索引。Hash索引把数据以hash形式组织起来,所以当查找某一条记录的时候,速度很是快。可是由于hash结构,每一个键只对应一个值,并且是散列的方式分布。因此它并不支持范围查找和排序等功能。

 

B+Tree索引

B+Tree是mysql使用最频繁的一个索引数据结构,是Inodb和Myisam存储引擎模式的索引类型。相对Hash索引,B+Tree在查找单条记录的速度比不上Hash索引,可是由于更适合排序等操做,因此它更受欢迎。毕竟不可能只对数据库进行单条记录的操做。

带顺序访问指针的B+Tree

B+Tree全部索引数据都在叶子节点上,而且增长了顺序访问指针,每一个叶子节点都有指向相邻叶子节点的指针。

这样作是为了提升区间效率,例如查询key为从18到49的全部数据记录,当找到18后,只要顺着节点和指针顺序遍历就能够以此向访问到全部数据节点,极大提升了区间查询效率。

大大减小磁盘I/O读取

数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每一个节点须要一次I/O就能够彻底载入。 

 

什么是索引

索引(Index)是帮助数据库高效获取数据的数据结构。索引是在基于数据库表建立的,它包含一个表中某些列的值以及记录对应的地址,而且把这些值存储在一个数据结构中。最多见的就是使用哈希表、B+树做为索引。

通常的应用系统,读写比例在10:1左右,并且插入操做和通常的更新操做不多出现性能问题,在生产环境中,咱们遇到最多的,也是最容易出问题的,仍是一些复杂的查询操做,所以对查询语句的优化显然是重中之重。提及加速查询,就不得不提到索引了。

 

为何要使用索引

咱们知道,数据库查询是数据库最主要的功能之一。而查询速度固然是越快越好。而当数据量愈来愈大的时候,查询花费的时间会随之增加。而索引,能够加速数据的查询。由于索引是有序排列的。

举个例子来讲,假设咱们有一个数据库表Employee,这个表分别有三个字段:name,age,address。假设表中有1000条记录。

假如没有使用索引,当咱们查询名为“Jesus”的雇员的时候,即调用:

select name,age,address from Employee where name = 'Jesus';

此时数据库不得不在Employee表中对这1000条记录一条一条的进行判断name字段是否为“Jesus”。这也就是所谓的全表扫描。

而当咱们在Employee表上的name字段上建立索引时,当咱们查询名为“Jesus”的雇员时,会经过索引查找去查询名为“Jesus”的雇员,由于该索引已经按照字母顺序排列,所以要查找名为“Jesus”的记录时会快不少,由于名字首字母为“J”的雇员都是排列在一块儿的。经过该索引,能获取到表中对应的记录。

举例说明使用索引的好处

假设索引(索引是一种数据结构)是链表结构。每一个节点存储的是关键字字段(这个例子中对应的是name属性)以及该关键字字段在数据库表的对应的记录的地址。而这些节点是根据name属性排序的(即根据字母顺序排序)。所以,当咱们执行上面说的查找名为“Jesus”的sql语句时,数据库会经过该索引来查询,由于该链表是有序排列的,在咱们找到第一个name属性为“Jesus”的节点后,继续日后找,当遇到name属性不为“Jesus”的节点时,就无需再日后查找了,由于节点是根据name属性有序排列的啊。假设第一个name=“Jesus”的节点是第499个节点,最后一个name=“Jesus”的节点是第500个节点,那么只须要遍历501个节点就能够了。当发现第501个节点的name字段不为“Jesus”,后面的499个节点也就无需遍历了。经过索引,咱们就找到了name为“Jesus”的节点,而经过该节点的另外一个属性(关键字字段在数据库表的对应的记录的地址),咱们就能获取到Employee表中知足条件name=“Jesus”的记录了。

经过使用索引,查询判断的次数就从1000次缩小到了501次了。起到了加速了查询效率。但实际上数据库中索引的结构,并非链表结构。

数据库中使用什么数据结构做为索引

数据库中实际使用的索引并不会是链表结构,由于效率过低了。 
咱们知道链表的查询效率是O(n)。就像上面的例子,遍历了501次才找到第一条符合条件的记录,这是很低效的。而咱们知道,数组+二分查找的效率是O(lgn),可是数组的插入元素以及删除元素的效率很低,所以使用数组作为索引结构并不合适。

另外,在选择数据库索引的结构的时候,要考虑到另外一个问题。索引是存在于磁盘中,当索引很是大的时候,达到几个G的时候,没法一次加载到内存中。

考虑到上面两个因素,数据库中索引使用的是树形结构。

各类树的名字

有这么几种树:

B-Tree
B+-Tree
B*-Tree

首先要明白三种树名中的“-”起到的是分隔的做用,并非“减”的意思。 

所以正确的翻译应该是B树,B+树,B*树。而不是B-树,B+树,B*树。所以,当你听到别人说“B减树”的时候,要明白它指的是B-Tree。即B树和B-树是同一种树。

为何要强调上面这一点呢,由于有的博文中写的是:B树是二叉树,B-树是多路搜索树。

然而B树和B-树都是指B-Tree。引用维基百科上的话:

B-tree 
Not to be confused with Binary tree.

 

也就是说,B-Tree并非Binart tree。B-Tree的中文名是平衡多路搜索树。 
(B树的相关介绍在下面)

平衡二叉树

树形结构是计算机系统里最重要的数据结构。

咱们知道,二叉树的查找的时间复杂度是O(log2N),其查找效率与深度有关,而普通的二叉树可能因为内部节点排列问题退化成链表,这样查找效率就会很低。所以平衡二叉树是更好的选择,由于它保持平衡,即经过旋转调整结构保持最小的深度。其查找的时间复杂度也是O(log2N)。

但实际上,数据库中索引的结构也并不是AVL树或更优秀的红黑树,尽管它的查询的时间复杂度很低。

为何平衡二叉树也不适合做为索引

以前说了平衡树的查找时间复杂度是O(log2N),已经很不错了,但仍是不适合做为索引结构。那么确定是有一种更适合做为索引的数据结构。那么这个更适合做为索引的数据结构,难道是查找的时间复杂度更低吗?并非。这种做为索引的数据结构的查找的时间复杂度也近似O(log2N)。

那为何平衡二叉树不适合做为索引呢?

索引是存在于索引文件中,是存在于磁盘中的。由于索引一般是很大的,所以没法一次将所有索引加载到内存当中,所以每次只能从磁盘中读取一个磁盘页的数据到内存中。而这个磁盘的读取的速度较内存中的读取速度而言是差了好几个级别。

注意,咱们说的平衡二叉树结构,指的是逻辑结构上的平衡二叉树,其物理实现是数组。而后因为在逻辑结构上相近的节点在物理结构上可能会差很远。所以,每次读取的磁盘页的数据中有许可能是用不上的。所以,查找过程当中要进行许屡次的磁盘读取操做。

而适合做为索引的结构应该是尽量少的执行磁盘IO操做,由于执行磁盘IO操做很是的耗时。所以,平衡二叉树并不适合做为索引结构。

B-Tree适合做为索引

平衡二叉树不适合做为索引。那么什么才适合做为索引——B树。

平衡二叉树没能充分利用磁盘预读功能,而B树是为了充分利用磁盘预读功能来而建立的一种数据结构,也就是说B树就是为了做为索引才被发明出来的的。

来看看关于“局部性原理与磁盘预读”的知识: 

局部性原理与磁盘预读:

因为存储介质的特性,磁盘自己存取就比主存慢不少,再加上机械运动耗费,磁盘的存取速度每每是主存的几百分分之一,所以为了提升效率,要尽可能减小磁盘I/O。为了达到这个目的,磁盘每每不是严格按需读取,而是每次都会预读,即便只须要一个字节,磁盘也会从这个位置开始,顺序向后读取必定长度的数据放入内存。这样作的理论依据是计算机科学中著名的局部性原理: 
当一个数据被用到时,其附近的数据也一般会立刻被使用。 
程序运行期间所须要的数据一般比较集中。 
因为磁盘顺序读取的效率很高(不须要寻道时间,只需不多的旋转时间),所以对于具备局部性的程序来讲,预读能够提升I/O效率。

 

搞清楚上面的意思。磁盘预读是具体实现,其理论依据是局部性原理。

为何说红黑树没能充分利用磁盘预读功能,引用一篇博文的一段话: 

红黑树这种结构,h明显要深的多。因为逻辑上很近的节点(父子)物理上可能很远,没法利用局部性,因此红黑树的I/O渐进复杂度也为O(h),效率明显比B-Tree差不少。

 

也就是说,使用红黑树(平衡二叉树)结构的话,每次磁盘预读中的不少数据是用不上的数据。所以,它没能利用好磁盘预读的提供的数据。而后又因为深度大(较B树而言),因此进行的磁盘IO操做更多。

B树的每一个节点能够存储多个关键字,它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点。也正因每一个节点存储着很是多个关键字,树的深度就会很是的小。进而要执行的磁盘读取操做次数就会很是少,更多的是在内存中对读取进来的数据进行查找。

B树的查询,主要发生在内存中,而平衡二叉树的查询,则是发生在磁盘读取中。所以,虽然B树查询查询的次数不比平衡二叉树的次数少,可是相比起磁盘IO速度,内存中比较的耗时就能够忽略不计了。所以,B树更适合做为索引。

比B树更适合做为索引的结构——B+树

比B树更适合做为索引的结构是B+树。MySQL中也是使用B+树做为索引。它是B树的变种,所以是基于B树来改进的。为何B+树会比B树更加优秀呢?

B树:有序数组+平衡多叉树; 
B+树:有序数组链表+平衡多叉树;

B+树的关键字所有存放在叶子节点中,非叶子节点用来作索引,而叶子节点中有一个指针指向一下个叶子节点。作这个优化的目的是为了提升区间访问的性能。而正是这个特性决定了B+树更适合用来存储外部数据。

引用一段话: 

走进搜索引擎的做者梁斌老师针对B树、B+树给出了他的意见(为了真实性,特引用其原话,未做任何改动): “B+树还有一个最大的好处,方便扫库,B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query很是方便,而B树不支持。这是数据库选用B+树的最主要缘由。 
好比要查 5-10之间的,B+树一把到5这个标记,再一把到10,而后串起来就好了,B树就很是麻烦。B树的好处,就是成功查询特别有利,由于树的高度整体要比B+树矮。不成功的状况下,B树也比B+树稍稍占一点点便宜。 
B树好比你的例子中查,17的话,一把就获得结果了, 
有不少基于频率的搜索是选用B树,越频繁query的结点越往根上走,前提是须要对query作统计,并且要对key作一些变化。 
另外B树也好B+树也好,根或者上面几层由于被反复query,因此这几块基本都在内存中,不会出现读磁盘IO,通常已启动的时候,就会主动换入内存。”

 

举个例子来对比。 
B树: 

好比说,咱们要查找关键字范围在3到7的关键字,在找到第一个符合条件的数字3后,访问完第一个关键字所在的块后,得遍历这个B树,获取下一个块,直到遇到一个不符合条件的关键字。遍历的过程是比较复杂的。

B+树(叶节点保存数据,其余的节点 所有存放索引): 

相比之下,B+树的基于范围的查询简洁不少。因为叶子节点有指向下一个叶子节点的指针,所以从块1到块2的访问,经过块1指向块2的指针便可。从块2到块3也是经过一个指针便可。

引用一篇博文中网友评论的一段话: 

数据库索引采用B+树的主要缘由是B树在提升了磁盘IO性能的同时并无解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。
B+树只要遍历叶子节点就能够实现整棵树的遍历。
并且在数据库中基于范围的查询是很是频繁的,而B树不支持这样的操做(或者说效率过低)。

正如上面所说,在数据库中基于范围的查询是很是频繁的,所以MySQL最终选择的索引结构是B+树而不是B树。 

 

2、索引的原理

一 索引原理

索引的目的在于提升查询效率,与咱们查阅图书所用的目录是一个道理:先定位到章,而后定位到该章下的一个小节,而后找到页数。类似的例子还有:查字典,查火车车次,飞机航班等

本质都是:经过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,咱们能够老是用同一种查找方式来锁定数据。

数据库也是同样,但显然要复杂的多,由于不只面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对全部的问题呢?咱们回想字典的例子,能不能把数据分红段,而后分段查询呢?最简单的若是1000条数据,1到100分红第一段,101到200分红第二段,201到300分红第三段......这样查第250条数据,只要找第三段就能够了,一会儿去除了90%的无效数据。但若是是1千万的记录呢,分红几段比较好?稍有算法基础的同窗会想到搜索树,其平均复杂度是lgN,具备不错的查询性能。但这里咱们忽略了一个关键的问题,复杂度模型是基于每次相同的操做成原本考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另一方面为了提升性能,每次又能够把部分数据读入内存来计算,由于咱们知道访问磁盘的成本大概是访问内存的十万倍左右,因此简单的搜索树难以知足复杂的应用场景。

 二 磁盘IO与预读

考虑到磁盘IO是很是高昂的操做,计算机操做系统作了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,由于局部预读性原理告诉咱们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据咱们称之为一页(page)。具体一页有多大数据跟操做系统有关,通常为4k或8k,也就是咱们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计很是有帮助。

3、索引的数据结构

任何一种数据结构都不是凭空产生的,必定会有它的背景和使用场景,咱们如今总结一下,咱们须要这种数据结构可以作些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么咱们就想到若是一个高度可控的多路搜索树是否能知足需求呢?就这样,b+树应运而生。

如上图,是一颗b+树,关于b+树的定义能够参见B+树,这里只说一些重点,浅蓝色的块咱们称之为一个磁盘块,能够看到每一个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P一、P二、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如1七、35并不真实存在于数据表中。

###b+树的查找过程
如图所示,若是要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找肯定29在17和35之间,锁定磁盘块1的P2指针,内存时间由于很是短(相比磁盘的IO)能够忽略不计,经过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,经过指针加载磁盘块8到内存,发生第三次IO,同时内存中作二分查找找到29,结束查询,总计三次IO。真实的状况是,3层的b+树能够表示上百万的数据,若是上百万的数据查找只须要三次IO,性能提升将是巨大的,若是没有索引,每一个数据项都要发生一次IO,那么总共须要百万次的IO,显然成本很是很是高。

###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的数据了, 这个是很是重要的性质,即索引的最左匹配特性。

这也是常常考察的,好比 我定义了 A,B,C的联合索引,若是 我只传递了 A,B 能走索引吗?答案是能,由于最左侧原理(百度问过) 

补充一下. 全文索引(FULLTEXT)=mysql的 myISAM搜索引擎默认的索引类型

      MySQL从3.23.23版开始支持全文索引和全文检索,fulltext索引仅可用于 MyISAM 表;他们能够从CHAR、VARCHAR或TEXT列中做为CREATE TABLE语句的一部分被建立,或是随后使用ALTER TABLE 或CREATE INDEX被添加。////对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,而后建立索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个很是消耗时间很是消耗硬盘空间的作法。
       文本字段上的普通索引只能加快对出如今字段内容最前面的字符串(也就是字段内容开头的字符)进行检索操做。若是字段里存放的是由几个、甚至是多个单词构成的较大段文字,普通索引就没什么做用了。这种检索每每以LIKE %word%的形式出现,这对MySQL来讲很复杂,若是须要处理的数据量很大,响应时间就会很长。 
  这类场合正是全文索引(full-text index)能够大显身手的地方。在生成这种类型的索引时,MySQL将把在文本中出现的全部单词建立为一份清单,查询操做将根据这份清单去检索有关的数据记录。全文索引便可以随数据表一同建立,也能够等往后有必要时再使用下面这条命令添加: 
  ALTER TABLE table_name ADD FULLTEXT(column1, column2) 
  有了全文索引,就能够用SELECT查询命令去检索那些包含着一个或多个给定单词的数据记录了。下面是这类查询命令的基本语法: 
  SELECT * FROM table_name 
  WHERE MATCH(column1, column2) AGAINST('word1', 'word2', 'word3') 
  上面这条命令将把column1和column2字段里有word一、word2和word3的数据记录所有查询出来。 

参考:Mysql索引详解及优化(key和index区别) 

四,索引使用注意事项

1,不要滥用索引

①,索引提升查询速度,却会下降更新表的速度,由于更新表时,mysql不只要更新数据,保存数据,还要更新索引,保存索引

②,索引会占用磁盘空间 

2,索引不会包含含有NULL值的列

复合索引只要有一列含有NULL值,那么这一列对于此符合索引就是无效的,所以咱们在设计数据库设计时不要让字段的默认值为NULL。 

3,MySQL查询只是用一个索引

若是where字句中使用了索引的话,那么order by中的列是不会使用索引的 

4,like

like '%aaa%'不会使用索引而like "aaa%"可使用索引


2、选择索引的数据类型

Mysql支持不少数据类型,选择合适的数据类型存储数据对性能有很大的影响。

(1)越小的数据类型一般更好:越小的数据类型一般在磁盘、内存和cpu缓存中都须要更少的空间,处理起来更快。

(2)简单的数据类型更好:整形数据比起字符,处理开销更小,由于字符串的比较更复杂。在MySQL中,应用内置的日期和时间数据类型,而不是字符串来存储时间;以及用整形数据存储IP地址。

(3)尽可能避免NULL:应该制定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,由于他们使得索引、索引的统计信息以及比较运算更加复杂。

3、MySQL常见索引有:主键索引、惟一索引、普通索引、全文索引、组合索引

1,INDEX(普通索引):ALTER TABLE 'table_name' ADD INDEX index_name('col')

最基本的索引,没有任何限制 

2,UNIQUE(惟一索引):ALTER TABLE 'table_name' ADD UNIQUE('col')

与“普通索引”相似,不一样的就是:索引列的值必须惟一,但容许有空值。 

3,PRIMARY KEY(主键索引):ALTER TABLE 'table_name' ADD PRIMARY KEY('col')

是一种特殊的惟一索引,不容许有空值。 

4,FULLTEXT(全文索引):ALTER TABLE 'table_name' ADD FULLTEXT('col')

仅可用于MyISAM和InoDB,针对较大的数据,生成全文索引很耗时耗空间

组合索引:ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3')

为了更多的提升mysql效率可创建组合索引,遵循“最左前缀”原则。建立复合索引应该将最经常使用(频率)作限制条件的列放在最左边,一次递减。组合索引最左字段用in是能够用到索引的。至关于创建了col1,col1col2,col1col2col3三个索引

 

1.MySQL索引背后的数据结构及算法原理(超赞的文章,2011年写的文章,厉害……) 
2.从B树、B+树、B*树谈到R 树(这篇文章做者也是好厉害,其博客访问量达千万) 
3.浅谈算法和数据结构: 十 平衡查找树之B树,而这篇博文里有B树和B+树插入元素的过程GIF图,超赞,有助于对B树和B+树的理解!

参考:一步步分析为何B+树适合做为索引的结构

参考:MySQL索引原理以及查询优化

相关文章
相关标签/搜索