索引,可能让好不少人望而生畏,毕竟每次面试时候 MySQL 的索引必定是必问内容,哪怕先撇开面试,就在日常的开发中,对于 SQL 的优化也而是重中之重。面试
能够绝不夸张的说,系统中 SQL 的好坏,是能直接决定你系统的快慢的。可是在优化以前你们是否想过一个问题?那就是:咱们优化的原则是什么?优化SQL的理论基础是什么?sql
虽说实践出真知,可是我更相信理论是支撑实践的基础,由于咱们不可能毫无目的的去盲目的实践,由于这样每每事倍功半。数据库
因此说了这么多只想告诉你们,在真正的开始索引优化以前,咱们须要完全搞明白索引的原理。这样再谈优化你将以为更丝滑~缓存
索引的本质是一种排好序的数据结构。这个我相信其实你们并不陌生,由于谈到索引不少人天然而然的就会联想到字典中的目录。markdown
没错,这样的类比是很形象的,可是若是再往深处说,恐怕不少小伙伴就有点张口结舌了,那既然你已经知道了索引的本质,那么您就已经有了看这篇文章的基础,相信读文本文的你,必定会对索引的原理有一个全新的了解。数据结构
在数据库中,索引是分不少种类的(千万不要狭隘的认为索引只有 B+ 树,那是由于咱们平时使用的基本都是 MySQL)。而不一样的种类很显然是为了应付不一样的场合,那索引到底有那些种类呢?下面就让咱们来大体的了解下。优化
Hash 索引是比较常见的一种索引,他的单条记录查询的效率很高,时间复杂度为1。可是,Hash索引并非最经常使用的数据库索引类型,尤为是咱们经常使用的Mysql Innodb引擎就是不支持hash索引的。主要有如下缘由:spa
对于 hash 索引,小伙伴们只须要了解到这里就能够了。设计
另外,常见的索引使用的数据结构是树结构,首先咱们来介绍下最经典的二叉树。3d
先来介绍下二叉树的特色:
首先来看一下二叉树的样子
可是在极端状况下会出现链化的状况,即节点一直在某一边增长。以下图
二叉树中,有一种特殊的结构——平衡二叉树,平衡二叉树的特色:
了解了二叉树以后,能够进一步谈一下什么是B树了。B 树大概是这样子的:
从B树的结构图中能够看到每一个节点中不只包含数据的 key 值,还有 data 值。
而每页的存储空间是有限的,若是 data 比较大,会致使每一个节点的 key 存储的较少,当数据量较大的时候,一样会致使B树很深,从而增长了磁盘 IO 的次数,进而影响查询效率。
好了,说到这里,常见的索引的种类也说完了,上面的内容仅仅是做为一个铺垫,下面咱们正式开始 MySQL 的 B+ 树。
MySQL 中最经常使用的索引的数据结构是 B+ 树,他有如下特色:
好了说了这么多的 B+ 树的特色,咱们来张图看看 B+ 树到底长什么样子(若是看不懂,也没有关系,下文会一步一步解释说明的)
上面的数据页就是实际存放数据页的地方,且数据页之间是经过双向链表进行链接的,好了到这里咱们就将各个索引的类型快速了解了下,下面咱们就开始正式B+树的分析。
咱们将上图中的数据页拿出来再细化下,就成了下面的这张图
咱们都知道 MySQL 在存储数据的时候是以数据页为最小单位的,且数据在数据页中的存储是连续的,数据页中的数据是按照主键排序的(没有主键是由 MySQL本身维护的 ROW_ID 来排序的),数据页和数据页之间是经过双向链表来关联的,数据与数据时间是经过单向链表来关联的。
也就是说有一个在每一个数据页中,他必然就有一个最小的主键,而后每一个数据页的页号和最小的主键会组成一个主键目录(就像上图中的左边部分),假设如今要查找主键为 2 的数据,经过二分查找法最后肯定下主键为 2 的记录在数据页 1 中,此时就会定位到数据页 1 接着再去定位主键为 2 的记录,咱们先知道大体的流程,细节先不要深究,先从宏观看结构原理,再到微观看实现原理。
刚刚上面是说的其实能够理解为是主键索引,主键索引也是最简单的最基础的索引。这个时候你们应该知道为何你创建了主键查询就能变快了吧?
可是如今假设有不少不少的是数据页,那是否是对应的主键目录会很大很大呢?
那假设有1000万条记录、5000万条记录呢?是否是就算是二分法查找,其效率也依旧是很低的,因此为了解决这种问题 MySQL 又设计出了一种新的存储结构—索引页。例若有下面这样状况,
假设上面的主键目录中的记录是很是很是多的,此时上面的结构是演变成这样子的,MySQL 会将里面的记录拆分到不一样的索引页中,也就是下面这样子的
索引页中记录的是每页数据页的页号和该数据页中最小的主键的记录,也就是说最小主键和数据页号不是单纯的维护在主键目录中了,而是演变成了索引页,索引页和数据页相似,一张不够存就分裂到下一张。
假如如今要查找 id=20 的这条记录,咦?那我应该到哪一个索引页中查找该条记录呢?因此这个时候确定是须要去维护索引页的。
没错,MySQL 也是这么设计的,也就是说 MySQL 同时也设计出了用于维护索引页的数据结构,其实也还叫索引页,只不过他们是在不一样的层级,相似下面这样子的:
也就是说维护索引页的索引页是在真正存储记录和数据页的索引页的上一层,如今若是你想查找 id=20 的这条记录,那就是从最上层的索引页开始查找,经过二分法查找,很快就可以定位到 id=20 s这条记录是在索引页 2 上,而后到就索引页 2 上面查找,接着就是和以前同样了(注意,索引页中的记录也是经过单向链表链接的),根据各个最小的主键可以定位到 id=20 是在数据页5上,假设数据页5是这样子的
那这个时候你是否是可以想明白数据是怎么定位的了呢?
好,既然你已经知道到索引页太多会往上一层扩散,那如今假设上一层的索引页记录也太多了,那该怎么办?很简单,继续分裂,再往上一层继续,不废话,我来画图帮助你们理解
我看明白了,你看明白了吗?咱们来模拟一个查找的过程,假设你要查找 37 这条记录,说实话我根本不知道这条记录在哪里。好,如今咱们就来模拟 MySQL 的查找过程,首先从最顶层的索引页开始查找,由于 id=37,所以定位到了索引页16,而后到索引页 16 中继续查找,此时一样可以定位到 id=37 在索引页 3 中,而后继续查找,最终可以定位到数据实在数据页 8 中,假设数据页 8 是这样子的
是否是很完美?若是非要我把上面的图画完整,那....小弟责无旁贷(图太大了,索引页中数据的链表结构就不画出来了)
这个时候机智的你是否是已经发现了什么小秘密?他是否是很像一颗二叉树?实际上这就是一颗 B+ 树的结构,这也是数据在磁盘中真正存储的物理结构。B+树的特性是什么呢?B+树,也是二叉搜索树的一种,可是他的数据仅仅存储在叶子节点(在这里就是数据页),像这种索引页+数据页组成的组成的B+树就是聚簇索引(这句话很重要)。
聚簇索引是 MySQL 基于主键索引结构建立的
可是如今问题又来了,既然这里强调的是主键索引,那咱们平时开发中除了主键索引其余的索引也用的很多,这时候该怎么办?假设你如今对name
、age
创建索引。如今回顾下主键索引,是否是在插入数据的时候基于主键的顺序去维护一个 B+ 树的?
而实际上非主键索引其原理是同样的,MySQL 都是去维护一颗 B+ 树,说白了,你创建多少个索引,MySQL 就会帮你维护多少的B+树(这下是否是也忽然想明白了为何索引不能创建太多了?之前就知道不能创建太多索引,由于索引也会占用空间,实际上这就是根本缘由)
假如如今真的对name+age
创建索引,那此时是存放的呢?此时 MySQL 根据会 name+age 维护一个单独的 B+ 树结构,数据依旧是存放在数据页中的,只不过是原来数据中的每条记录写的是 id=xx,如今写的是name=xx,age=xx,id=xx,无论怎么样,主键确定会存放的,先来张图压压惊
在插入数据的时候,MySQL 首先会根据 name 进行排序,若是 name 同样,就根据联合索引中的 age 去排序,若是还同样,那么就会根据 主键 字段去排序。插入的原理就是这样子的。
此时每一个数据页中的记录存放的实际是索引字段和主键字段,而其余字段是不存的(为何不存放?同样的数据处处存放很浪费空间的,也不必,因此才会有下面的索引优化),至于查找,原理和过程跟聚簇索引同样,这里就再也不赘述,可是,下面说的内容倒是相当重要的:假设如今执行这样的SQL:
SELECT name FROM student WHERE name='wx'
复制代码
那么此时的查询是完美的,使用到了索引且不须要回表
是这样子的,如今要根据 name 查找到该条记录,且查询的字段(即 select 后面的查询字段)也仅仅有 name(只要是在 name,age,id 这三个字段中均可以)这个时候是可以直接获取到最终的记录的
换句话说,由于联合索引中的记录也仅仅有 name,age,id,因此在查询的若是也仅仅查询这三个字段,那么在该B+树中就可以查询到想要的结果了。
那如今假设查询的 SQL 是这样子的(咱们假设 student 中还有除了name,age,id 其余的字段 )
SELECT * FROM student WHERE name='wx'
复制代码
那这下子就完蛋了,由于你如今虽然根据 name 很快的定位到了该条记录,可是由于 name+age 不是聚簇索引,此时的 B+ 树的数据页中存放的仅仅是本身关联的索引和主键索引字段,并不会存其余的字段,因此这个时候其余的属性值是获取不到的,这时候该怎么办?
这种状况下,MySQL 就须要进行回表查询了。此时 MySQL 就会根据定位到的某条记录中的 id 再次进行聚簇索引查找,也就是说会根据 id 去维护 id 的那么 B+ 树中查找。由于聚簇索引中数据页记录的是一条记录的完整的记录,这个过程就叫回表。
再强调下回表的含义:根据非主键索引查询到的结果并无查找的字段值,此时就须要再次根据主键从聚簇索引的根节点开始查找,这样再次查找到的记录才是完成的
。
最后,让我一块儿看下 MySQL 对于非主键索引的维护过程:
对于非主键索引(通常都是联合索引),在维护 B+ 树的时候,会根据联合索引的字段依次去判断,假设联合索引为:name + address + age,那么 MySQL 在维护该索引的 B+ 树的时候,首先会根据 name 进行排序,name 相同的话会根据第二个 address 排序,若是 address 也同样,那么就会根据 age 去排序,若是 age 也同样,那么就会根据主键字段值去排序,且对于非主键索引,MySQL 在维护 B+ 树的时候,仅仅是维护索引字段和主键字段。