索引是为了加速对表中数据行的检索而创建的一种分散的存储结构,对于大的表,索引是非常有必要的,通过索引,能够显著地提高数据查询的效率,因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。
InnoDB存储所有支持常见的索引:
B+树索引、全文索引、哈希索引
。B+树索引是传统意义上的索引,是目前关系型数据库系统中查找最为常用和最为有效的索引。为什么innodb存储引擎中的采用B+树数据结构实现索引?
在介绍B+树之前,需要先了解一个二叉查找树,B+树是通过二叉查找树,再有平衡二叉树,B树演化而来的。那么多的树结构?为什么MySQL偏偏采用B+树作为索引? 来看看他们之间的区别。
二叉树(binary tree) 是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树,左子树和右子树又同样都是二叉树,二叉树又分为满二叉树
、完全二叉树
和二叉排序树(二插查找树)
,二叉树示意图:
如果二叉树中除去最后一层节点其它为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树
。
如果二叉树中除了叶子结点,每个结点的度都为2,则此二叉树称为满二叉树
。
满二叉树除了延续二叉树的性质,还具备一下性质:
二叉查找树
也被称为二叉搜索树
或者二叉排序树
,它主要特性是:二叉查找树的特点是一个节点的左子树的所有节点都小于这个节点,右子树的所有节点都大于这个节点,这样我们在查询数据时,不需要计算中间节点的位置了,只需将查找的数据与节点的数据进行比较。
但是,因为二叉搜索树不存在平衡算法,二叉查找树也有可能是这样的结构:
这个时候可以看到我们的二叉查找树变成了一个线性链表
。出现蹩脚的情况,设计者们发现降低树的高度自然就可以提高查找效率。那么如何解决降低树的高度的问题?在这种基础上设计者给二叉树加入了平衡算法,出现了平衡树。
B-tree的示意图如下:
图中的p节点为指向子节点的指针,每个节点称为页,页就是我们上面说的磁盘块,在mysql中数据读取的基本单位都是页,所以我们这里叫做页更符合mysql中索引的底层数据结构。
B+树是对B树的进一步优化,我们可以看看B+树的数据结构图:
根据上面的两张图可以明细看出,它与B树的区别是:
1️⃣ B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,innodb中页的默认大小是16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。另外,B+树的阶数是等于键值的数量的,如果我们的B+树一个节点可以存储1000个键值,那么3层B+树可以存储1000×1000×1000=10亿个数据。一般根节点是常驻内存的,所以一般我们查找10亿数据,只需要2次磁盘IO。
2️⃣ 因为B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而B树因为数据分散在各个节点,要实现这一点是很不容易的。
有心的读者可能还发现上图B+树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。
其实上面的B树我们也可以对各个节点加上链表。其实这些不是它们之前的区别,是因为在mysql的innodb存储引擎中,索引就是这样存储的。也就是说上图中的B+树索引就是innodb中B+树索引真正的实现方式,准确的说应该是聚集索引
通过上图可以看到,在innodb中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
MyISAM中的B+树索引实现与innodb中的略有不同。在MyISAM中,B+树索引的叶子节点并不存储数据,而是存储数据的文件地址。
:这个问题的简单回答是:约2千万,是怎么算出来的呢?要搞清楚这个问题,需要从InnonDB索引数据结构、数据组织方式说起。
假设数据表中的数据都是存储在页中的,所以假设一行数据的大小是1k,那么一个页可以存放16行这样的数据,假设B+树的高度为2的话,即有一个根结点和若干个叶子结点。这棵B+树的存放总记录数为=根结点指针数*单个叶子节点记录行数。
- 如果一行记录的数据大小为1k,那么单个叶子节点可以存的记录数 =16k/1k =16.
- 非叶子节点内存放多少指针呢?我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,所以就是8+6=14字节,16k/14B =16*1024B/14B = 1170
因此,一棵高度为2的B+树,能存放1170 * 16=18720条这样的数据记录。同理一棵高度为3的B+树,能存放1170 *1170 *16 = 21902400,也就是说,可以存放两千万左右的记录。B+树高度一般为1-3层,已经满足千万级别的数据存储。
:对于面试官这个问题,需要从他们之前的数据结构差异说起:
- Hash哈希,只适合key的等值查询,不适合范围查询;
- 普通二叉树,可能会一种特殊化的链表,查询效率较低;
- 红黑树,是一种特化的平衡二叉树,MySQL 数据量很大的时候,索引的体积也会很大,内存放不下的而从磁盘读取,树的层次太高的话,读取磁盘的次数就多了;
- B-Tree,叶子节点和非叶子节点都保存数据,相同的数据量,B+树更爱矮壮,也是就说,相同的数据量,B+树数据结构,查询磁盘的次数会更少。
:B-树和B+树的主要区别是:
- B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据;
- B+树中各个页之间是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的,而B树不是;
本篇文章介绍个二叉树、二插查找树、B树、B+树,对比了他们之间的区别,并且详细说明了为什么mysql用B+树作为数据的索引,以及在innodb中数据库如何通过B+树索引来存储数据以及查找数据。