扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,便可关注微信公众号,阅读更多Spring源码分析
、Java并发编程
和Netty源码系列
文章。web
在上一篇博客中详细说明了 MySQL 的索引使用的是 B+Tree 这种数据结构,而不是 B-Tree,然而平时咱们接触到了不少高效的数据结构,例如数组、哈希表、二叉搜索树、红黑树等,那为何 MySQL 不选择这些数据结构做为索引呢?redis
MySQL 做为存储数据的组件,它的主要操做就是数据的增删改查,其中查询操做又是重中之重。咱们常常所说的数据库优化,大部分优化的就是查询相关的操做。所以一个数据库选择何种数据结构做为索引,主要考虑因素就是这种数据结构对增删改查操做的效率如何,尤为是查询操做(一般查询操做包括等值查询、范围查询等)。本文接下来将从增删改查的角度来分析,为何 MySQL 最终选用的是 B+Tree 做为索引的数据结构。数据库
由于 MySQL 在 5.5 版本之后,默认的存储引擎是 InnoDB,因此本文所讨论的内容,全是基于存储引擎是 InnoDB 为前提。编程
数组这种数据结构,应该是咱们接触编程起,最早遇到的数据结构了。数组是内存中一块连续的内存空间,定义一个数组对象,这个对象的指针指向了这块内存的起始地址,若是知道数组元素的下标,那么就能够计算出该下标所对应的元素的内存地址了,所以能够在 O(1)的时间复杂度内获取到元素,很是快速。数组
对于一个有序数组,它的查找过程可使用二分查找法进行查找,时间复杂度为 O(logn),效率也很是高。由于是有序数组,所以对范围查找也十分友好,只须要找到起始元素便可。若是在知道元素下标的状况下,更新操做也很是快,对于删除操做,若是咱们不考虑空洞的话(若是直接将对应下标处的元素置为 null,这样这块连续内存块中至关于有个空洞),删除操做也很快。微信
这么一分析,数组对查询、删除、更新操做的效率很是高,选数组做为 MySQL 索引的数据结构看起来彷佛不错。然而咱们忽略了还有插入操做,若是咱们要往数组中间插入一个数据,咱们须要将数组中要插入的目标位置后的全部元素先日后挪动一个位置,而后才能插入新的数据,也就是涉及到了数组的复制操做,要插入的数据越靠前,那么咱们须要复制的数据就越多,这个不只须要额外开辟内存,复制数据消耗的时间也很长。而咱们在平时的开发中,生产环境的一张表的大小动辄就 1GB 以上了,若是要往中间插入一条数据时,那得复制多少数据啊!数据结构
所以,从插入数据这一角度来看,数组不太适合做为 MySQL 索引的数据结构。并发
哈希表是一种 key-value 形式的数据结构,底层采用数组+链表结构来实现,它是将 key 经过一个哈希函数计算出一个数字,而后以该数字做为数组的下标,而后将 value 存放到对应下标的数组中。对于不一样的 key,在通过哈希函数计算后,可能出现相同的值,这种状况叫作哈希冲突,这时候就意味着同一个数组下标处要存放两个元素了,因此这个时候将数组中的元素变为一个链表,经过链表将这两个元素串联起来。编辑器
根据上面哈希表的特色来看,哈希表对于删除、查找、更新、插入操做,都是先根据 key 计算出一个值,就能定位到数据的目标位置了,时间复杂度都是 O(1),速度特别快。可是咱们在使用 MySQL 时,常常会遇到查找某个范围内的数据,例如 between...and、>=、<=等范围查找操做。这个时候哈希表应该怎么办呢?函数
由于哈希表的全部 key 都会通过哈希函数计算,而后再存放数据,原本可能有序的 key,但通过哈希函数计算出来的值就不是有序的了,因此这个时候,若是要在哈希表中进行范围查找,就只能对整个哈希表进行遍历了,只有符合条件范围的数据,才取出来。当咱们数据库中的数据愈来愈多,达到几百万甚至几千万条的时候,这个时候,对整个表的遍历是很是耗时的。
所以,从范围查询的场景来看,哈希表也不太适合做为 MySQL 索引的数据结构。哈希表适用于什么场景呢?适用于等值查询的场景,最经典的场景就是 NOSQL 数据库,例如咱们最经常使用的 redis,redis 中不就是全都是 key-value 吗?
实际上,MySQL 中也有地方使用哈希做为索引。使用 MySQL 的同窗大部分都应该使用过 Navicat 这款工具吧,这是一个可视化的 MySQL 客户端远程链接工具,在这个工具中,咱们能够在可视化界面中进行数据库的相关操做,例如增删改查、修改表结构、建立索引等。在建立索引时,咱们其实能够选择索引的数据结构,它有两个选项:B+Tree 和 HASH,若是你不勾选,默认状况下是 B+Tree。以下图所示。
一般状况下,不建议将索引的结构选为 HASH,除非业务的场景的确符合 key-value 这种场景,例如业务系统的一些 key-value 形式的配置项的表、数据字典等功能。
每一个节点最多有两个子结点的树称之为二叉树,比较特殊且经常使用的二叉树有二叉搜索树、AVL 树(平衡树)、红黑树等。
对于二叉搜索树而言,它的查找操做的时间复杂度就是树的高度,最理想的状况下,也就是满二叉树的状况下,查找的时间复杂度为 O(logn)。当咱们在不停地动态地往树中插入数据、删除数据时,在极端状况下,二叉搜索树可能退化成链表,它的查找时间复杂度就变成了 O(n),性能不够稳定。
平衡树是在二叉查找树的基础上,增长了一条限制,左右两个子树的高度差不能超过 1,左右两边相对平衡,所以称之为平衡树。而平衡树在数据动态地删除、插入地过程当中,为了维护平衡,避免树退化成链表,所以须要在删除或者插入数据后进行额外的旋转操做,会损耗必定的性能,但总体来说,它的查找、删除、插入、更新的复杂度均为 O(logn)。它的中序遍历,数据是有序的,所以也适合范围查找。可是它的缺点是,为了维护平衡,它的旋转操做过于复杂。
在平衡二叉树的基础上又出现了红黑树,关于红黑树的性质就在这里不细说了,内容太多了,后面会单独写博客介绍。总体上来讲,红黑树是一种近似平衡(不彻底平衡),结点非黑即红的树,它的树高最高不会超过 2logn,所以查找的时间复杂度为 O(logn),不管是增删改查,它的性能都十分稳定。工程上,不少地方都使用的是红黑树这种数据结构,例如 Java 中的 HashMap、TreeMap 等。
咋一看,AVL 树和红黑树的增删改查性能都十分稳定,虽然中间涉及到不少旋转操做,实现过于复杂,可是仍是能用代码实现出来的,只要能实现出来,这都不是事啊,性能好、够稳定就行,那为何不用 AVL 树和红黑树去做为 MySQL 的索引数据结构呢?
这是由于不管是二叉搜索树,仍是 AVL 树,亦或是红黑树,它们都是二叉树的一种,特色都是每一个结点最多只有两个子结点,若是存储大量数据的话,那么树的高度会很是高。而 MySQL 存储的数据最终是要落地到磁盘的,MySQL 应用程序读取数据时,须要将数据从磁盘先加载到内存后才能继续操做,因此这中间会发生磁盘 IO,而若是树过高,每遍历一层结点时,就须要从磁盘读取一次数据,也就是发生一次 IO,若是数据在树高为 20 的地方,那查找一次数据就得发生 20 次 IO,这对应用程序简直就是灾难性的,耗时太长了。所以二叉树在 MySQL 这种须要存储大量数据的场景下,是不适合当作索引的数据结构的,由于树过高,操做数据时会发生屡次磁盘 IO,性能太差。
既然二叉树由于每一个结点最多只有两个子结点,最终在存储大量数据时致使树高过高,所以不适合当作 MySQL 的索引,那么让树的每一个结点尽量多的拥有多个子结点,也就是多叉树,那这样在大量储存数据时,树高就低不少了,那这样总应该能够了吧!而多叉树中典型的例子有 B-Tree 和 B+Tree。
B-Tree 的特色是不管叶子结点和非叶子结点,它都存有索引值和数据;B+Tree 的特色是只有叶子结点才会存放索引值和数据,非叶子结点只会存放索引值自己。所以对于非叶子结点,一个结点中,B+Tree 存放的索引值数量会远远大于 B-Tree(由于一个结点的空间是有限的,B-Tree 要存放索引+数据,而 B+Tree 只须要存放索引),这样就致使了每一个结点中,B+Tree 能向下分出更多的叉,子结点数更多。
那么在要存储一样大小的数据文件的场景下,用 B+Tree 存储,最终树的高度会远远小于用 B-Tree 存储的高度,因此使用 B+Tree 做为 MySQL 索引的数据结构,未来在读取数据时,发生的磁盘 IO 次数会更少,性能更优,所以最终 MySQL 索引的数据结构使用的是 B+Tree。
若是你想更加详细的了解 B+Tree 和 B-Tree 的知识,能够阅读如下这两篇文章:索引数据结构之 B-Tree 与 B+Tree(上篇) 和 索引数据结构之 B-Tree 与 B+Tree(下篇)。
其中《索引数据结构之 B-Tree 与 B+Tree(下篇)》这篇文章的后半部分,花了很长的篇幅,说明了为何 MySQL 要使用 B+Tree 而不是 B-Tree 来做为索引的数据结构。
本文从每一种数据结构的特色出发,详细分析了为何 MySQL 最终要选用 B+Tree 做为索引的数据结构,但如今为止,相信你应该对 MySQL 的索引有了必定的了解。在平时接触 MySQL 的过程当中,相信你必定据说过不少索引名词,例如:主键索引、聚簇索引、非聚簇索引、二级索引、惟一索引、覆盖索引、全文索引、联合索引等等,那你知道它们分别是什么意思吗?下一篇文章介绍。