MySQL索引那些事

原文连接html

你们有没有遇到过慢查询的状况,执行一条SQL须要几秒,甚至十几、几十秒的时间,这时候DBA就会建议你去把查询的 SQL 优化一下,怎么优化?你能想到的就是加索引吧?java

 

为何加索引就查的快了?这就要从索引的本质以及他的底层原理提及。mysql

 

索引是什么

 

那索引究竟是什么呢?你是否是还停留在大学学『数据库原理』时老师讲的“索引就像字典的目录”这样的概念?老师讲的没错,但没有深刻去讲。算法

 

其实索引就是一种用于快速查找数据的数据结构,是帮助MySQL高效获取数据的排好序的数据结构。sql

 

索引的好处

 

举例说明索引的好处以及是怎么加快查询的。数据库

 

假设咱们有一个表t,它有俩字段,Col1和Col2,以下:windows

 

image.png

 

不加索引centos

不加索引的状况下,SQL: select * from t where t.col2=89 ,须要从表的第一行一行行遍历比对col2的值是否等于89,这样须要比对6次才能查到。这只是只有几行记录的表,那若是是百万级千万级的表呢?是否是就比较的次数就更多了,那还不得慢死。数据结构

 

加索引mybatis

若是col2这列加了索引,mysql内部会维护一个数据结构。假设mysql用的数据结构是红黑树(右子树的元素大于根节点,左子树的元素小于根节点)的数据结构创建索引,那就像上图右边那样。这样的话,刚才的那条SQL是否是只须要2次磁盘IO就查到了,是否是快不少了。

 

这就是索引的好处。索引使用比较巧妙的数据结构,利用数据结构的特性来大大减小查找遍历次数。

 

索引底层数据结构的探索

既然索引底层原理是利用一些巧妙的数据结构维护咱们的数据,使得查询效率很高,那索引底层使用的什么数据结构呢?又是怎样来维护咱们的数据呢?下面就带着你们一块儿探索一下索引的底层数据结构。

 

索引可选的数据结构 :

  • 二叉树
  • 红黑树
  • hash
  • B-tree

 

但mysql索引的底层用的并非二叉树和红黑树。由于二叉树和红黑树在某些场景下都会暴露出一些弊端或者说缺点。

 

二叉树

 

咱们看一下二叉树若是做为索引的底层数据结构在什么样的场景下有怎么样的缺点和不足。

 

假设把刚才的SQL改一下,用col1做为条件来查找,SQL: select * from t where t.col1 = 6 。

 

假如把col1做为索引,col1这列的数据特色是从上到下依次递增,相似于自增主键,那在每插入一行在维护二叉树这样一个数据结构的时候,咱们看一下二叉树维护成什么样子了。

 

打开这个网址(国外的),能够演示数据结构维护的过程。依次插入一、二、三、四、5...

经过这个网站的演示插入这些数据,咱们能够看到这样的一个二叉树是否是一直在单边增加,没有左子树。再仔细看一下和咱们学过的链表是否是很像,也就是说二叉树在某些场景下退化成了链表。

 

二叉树插入演示.gif 

  image.png

链表的查找是否是须要从头部遍历啊,这时候和没加索引从表的第一行遍历是否是没什么太大区别?这就是mysql索引底层没有使用二叉树这种数据结构的缘由之一。

 

当二叉树像上图同样退化成链表后,咱们去查col1=6的记录是否是从二叉树的根节点依次遍历,遍历6次才能查到,和不加索引从表里一行行的遍历没太大差异。这是二叉树所谓索引底层数据结构的弊端之一。

 

红黑树

 

那有没有更好的数据结构用来存储索引,帮助咱们更快的查找呢?比方说红黑树或hash表。

 

咱们先看下红黑树。红黑树是什么?

是一种平衡二叉树,JDK1.8的hashmap就用到了红黑树。

 

那咱们把刚才的同样的数据用红黑树来看一下是什么样的效果,一样打开刚才的网址,咱们选择红黑树。

 

image.png

 

依次插入一、二、三、四、五、六、7看一下效果,能够看到,当有单边增加的趋势时红黑树会进行一个平衡(旋转)。这时,咱们查询col1=6的数据时,查了3次,比二叉树又有了改进。红黑树演示.gif

 image.png

 

先告诉你mysql索引用的数据结构也不是红黑树,而是B+Tree(B-Tree的变种)。那为何MySQL也没用红黑树作索引的数据结构呢?说白了红黑树仍是有缺陷的。

 

红黑树作索引底层数据结构的缺陷

咱们能够想一下,对于一些大公司特别是互联网公司,表数据动辄数百万数千万,那这样的表咱们能够想象一下,如今咱们只有7条记录,树的高度就达到了4层,那数百万数千万甚至上亿记录的表建立的索引它的树高得有多高?

 

假如说我查找的数据在底层的叶子节点上,通常来讲都是从根节点开始查找,假如树的高度是50,那我要进行50次查找,50次磁盘IO那得多慢啊这开销已经很大了。这就是红黑树做为索引数据结构的弊端:树的高度太高致使查询效率变慢。

 

那能不能作一点改造呢?咱们看,红黑树的树越高遍历次数会越多,会由于树的高度影响查询效率。因此咱们要解决的问题就是减小树的高度,尽可能控制它的高度在一个阈值范围内。假设说不大于5,即便数据达到1千万2千万最多也就5次磁盘IO就找到了,5次磁盘IO也是能够接受的毕竟表数据这么大嘛。

 

怎么改造能达到这个效果呢????想一下,既然树的高度不让增长,又想存不少数据。也就是说限制了纵向发展,那就横向发展呗。(身高已经增加不了了,长胖仍是能够的)

 

对于上图的红黑树来讲每一个节点的子节点最多就2个,那基于横向增加的思想就让他变成3叉、4叉、5叉.....让子节点增长,让每个高度能够存储更多的索引元素,每一个节点又分叉,分出来的叉又有不少个节点。那么存储同等数量级别的数据,横向存储的越多,树高就越小了。这样的一个改造结果就是B-Tree。

 

Hash

待会儿有别的问题会引入hash。

 

B-Tree

  • 叶节点具备相同的深度,叶节点的指针为空
  • 全部索引元素不重复
  • 节点中的数据索引从左到右递增排序

 

image.png

 

就这样的一个结构。也就是说在一个节点上能够存储更多的元素,k-v,key就是索引字段,data就是索引字段所在的那一行的数据或是那一行数据坐在的的磁盘文件地址、指针,再去查找元素的时候一次性不是Load一个小元素,而是把一个大的节点的数据一次性所有load到内存,而后再在内存里再去比对,在内存里操做是比较快的。

 

若是咱们要查找49这个元素,其实是从根节点开始查找的,它一次性将根节点这个大节点一次性load到内存里,而后用要查找的元素在这里去比对,49大于15小于56,在15和56之间有一个节点存储的是下一个节点的磁盘地址指向下一个节点(这个节点的索引都是大于15小于56的),而后再将这个节点一次性load到内存去找这个元素,而后比对就找到了。

注意,一次load节点是一次磁盘IO,是很是慢的,可是咱们把它load到内存中以后在你内存里随机的找某一个元素是很是快的,跟一次磁盘IO这个时间消耗去比对的话几乎能够忽略不计。

 

那按这种说法树的高度越小越好,那按这种思路可不能够把一个表的数据都放到一个大的节点上?而后把这个节点一次性load到内存里,我再在内存里一个个去比对不行吗?不是说内存里去比较查找元素是很是的快嘛,跟一次磁盘IO去比对快的多。不能够这样吗?

 

答案是否认的。

凡事都有个度。你想一想,假如咱们有几千万数据,在磁盘上面所有放到一个节点上去是不可能的,你的数据表是一行行插入的,存在磁盘上面几百兆甚至几个G,一次性load到内存中合适吗?内存原本就有限,一次性load这么大的数据,并且若是你学过计算机组成原理你也知道,磁盘IO跟内存打交道的单位是4K,一次可能读取4K的数据,可能有时候有一些局部读取的原理可能会取几十K(4的整数倍),取个16K,24K也是能够的 。可是一次交互取这么大是搞不定的,这是计算机组成原理定的,一次磁盘IO取那么多数据,对内存也是很是的浪费,并且这一次磁盘IO也是很是慢的。因此这个节点的大小设置要合适,不能太大也不能过小,mysql对这个节点大小设置的是16K,用下面这个SQL就是能够查到 show clobal status like 'Innodb_page_size' 。

 

image.png

 

为啥设置16K?为何不是更大的如16M呢,16K已经足够用了。

 

MySQL索引选择的不是原生的B-Tree,而是对他进行了改造,获得的是一种叫作B+Tree的数据结构

 

B+Tree(B-Tree变种)

  • 非叶子节点不存储data,只存储索引(冗余),能够放更多的索引
  • 叶子节点包含全部索引字段
  • 叶子节点用指针链接,提升区间访问的性能

 

image.png

 

和B-Tree有啥区别?非叶子节点没有数据,数据都挪到叶子节点,叶子节点之间还有指针,非叶子节点之间跟原来同样没有指针。

 

为啥data元素挪到叶子节点?非叶子节点只存储索引元素,叶子节点存储了一份完整表的全部行的索引字段,data元素是每一个索引元素对应要查找的行记录的位置或行数据,这样非叶子节点的每一个节点就能够存储更多的索引元素(等会会有一个大体的估算)。实际上非叶子节点存储的是一些冗余索引,看一下上图,15/20/49,选择的是整张表的哪些数据做为索引?选择的是处于中间位置的,由于它要用到B+Tree一些比大小去查找,B+Tree本质能够叫作多叉平衡树,单看B+Tree的某一小块他仍是一个二叉树。

 

image.png

 

还有一个特色,某一个节点的元素处于一个递增的顺序,会提取叶子节点的一些处于中间位置的数据做为冗余索引,查找的时候从根节点开始查找,先把根节点加载到内存里去,而后在内存里去比对。

 

image.png

 

好比要查找索引为30的数据,先在根节点跟15去比较,大于15,而后小于56,而后从他俩中间的指针查找下一个节点把它load到内存,再在内存里去比对,大于15,大于20,而后小于49,就根据20和49之间的指针找到下一个节点,而后loa到内存,去比对,不等于20下一个30,相等,OK了。

 

为何把中间的元素提取出来作冗余元素,为的是查找效率更高。

 

回到刚刚的问题,为啥要搞这些冗余索引,并且把这些冗余索引的data元素搞到叶子节点?也就是说B+Tree至关于与B-Tree来讲个人非叶子节点是不存储data元素的,叶子几点才存储data元素?

你想一下,一个节点不能太大也不能过小,就是16K,把data元素挪走之后,是否是这个节点就能存更多的冗余索引了,意味着分叉就更多了,意味着叶子节点就能存储更多的数据了。

 

假设索引字段类型是Bigint,8bit,每两个元素之间存的是下一个节点的地址,mysql分配的是6bit,也就是说一个索引后面配对一个节点地址,成对出现,能够算一下16K的节点能够存多少对也就是多少个索引,8b+6b=14b,16K /14b=1170个索引,叶子节点有索引有data元素,假设占1K,那一个节点就放16K/1K=16个元素,假设树高是3,全部节点都放满,能放多少数据?能够算一下,1170*1170*16=21902400,2千多万,mysql设置16K的大小,数据就能够存2千多万就已经足够了吧,既能保证一次磁盘IO不要Load太多的数据 又能保证一次load的性能,即使表的数据在几千万的数量也能保证树的高度在一个可控的范围。

 

能够看一下几千万的数据表是否是加了索引几十毫秒几百毫秒就出结果了,因此就解释了几千万的表精确的使用索引后他的性能依旧比较高。

 

树的高度只有3的状况下就能存储2千多万的数据,即使某一个索引在叶子节点,那也就二、3次磁盘IO就能查找到,固然很快了。并且mysql底层的索引他的根节点,是常驻内存的,直接就放到内存的,查找叶子节点,一个2千万的数据放到B+Tree上面,要查找叶子节点,就只须要2次磁盘IO就搞定了,在内存里比对的时间基本能够忽略。

 

MySQL是如何存储索引和数据的

 

刚才讲的原理性的比较多,如今结合具体的mysql的表不一样的索引来看一下它底层究竟是如何运用B+Tree来维护索引的。

索引和数据存放位置是哪?

首先问下mysql的表、数据、索引是放到那里的?

磁盘=》默认是安装目录的data文件里(不一样版本可能有所不一样),每一个数据库对应data文件夹里的一个文件夹

 

image.png

 

咱们打开一个walking_mybatis数据库看一下有一个user表,再打开对应的文件夹看一下,里面的文件名和表名有关系,而后有不一样的后缀,这里面的不一样的放法和mysql的存储引擎有关,和你选择的哪一种存储引擎有关。

 

image.png

 

存储引擎是修饰什么的?

你们都知道,mysql常见的存储引擎有InnoDB存储引擎,MYISAM存储引擎,那存储引擎是形容mysql数据库的仍是某一张表的?

 

是表,尽管数据库级别也有存储引擎选项,但最终仍是以表的存储引擎为主的。

 

若是你用Navicat工具去建表,也许你最多就用了“字段”这一栏去增长字段,你能够点一下“选项”看一下,能够选择存储引擎。

 

image.png

 

我这边又新建一个order表,而后选择为MYISAM存储引擎

 

image.png

 

在表上右键选择『对象信息』->『DDL』查看

 

image.png

 

看一下user表的

 

image.png

索引和数据文件

 

再来看一下这个数据库文件夹下这俩表的数据文件。

 

image.png

 

咱们会发现,user表(InnoDB存储引擎)对应两个文件,order表(MYISAM存储引擎)对应3个文件。其中.frm文件是存储的是表结构,两个存储引擎都同样,而InnoDB的.ibd文件是索引+数据,MYISAM的.MYI(I:index)和.MYD(D:data)文件分别是索引字段的索引结构和数据文件,也就是说MYISAM存储引擎的索引和数据是分开的,而InnoDB存储引擎的数据和索引是在一个文件里的。

InnoDB和MYISAM的一些不一样

MYISAM存储引擎

MYISAM索引实现(非汇集)

  • 索引文件和数据文件是分离的(非汇集)

 

数据、行记录是存储在MYD文件,假如col1是索引字段那么这一列是存储在MYI文件里以B+Tree的结构来组织的,而后他的叶子节点的data部分存储的是索引所在行记录的磁盘文件地址,根据磁盘文件地址指针就能够从MYD文件里快速的找到咱们的这一行记录。

 

查找过程

因此MYISAM这个存储引擎他的查找的一个大体过程就是,先看条件字段有没有用到索引,是索引字段就先去到索引文件去查找这个索引所在的那一行的磁盘文件地址,就借助B+Tree的特色从根节点顺藤摸瓜找到磁盘文件地址指针,而后从MYD文件一次性定位到所找的数据,也就是说MYISAM会垮两个文件。

 

image.png

 

 

InnoDB存储引擎


InnoDB索引实现(汇集)

  • 表数据文件自己就是按B+Tree组织的一个索引结构文件
  • 汇集索引-叶子节点包含了完整的数据记录
  • 为何InnoDB表必须有主键,而且推荐使用整型的自增主键?
  • 为何非主键索引结构叶子几点存储的是主键值?(一致性和节省存储空间)

 

用的最多的InnoDB存储引擎是什么样子的呢?咱们能够看到,它只有两个文件。.rm文件和MYISAM同样都是表结构文件,.ibd文件就是MYISAM的MYI和MYD文件的合并,索引文件和数据文件都存储到一个文件。

 

InnoDB存储引擎索引存储结构大概是下图这样的,它也是一个B+Tree,可是它的叶子节点和MYISAM有点区别,它存储的是索引所在行的全部字段。

image.png

 

这个好处是是什么?不用回表了,性能应该比MYISAM高,你看MYISAM查找到索引所在行记录的磁盘地址后还要回MYD文件读取一次。

 

汇集索引/非汇集索引

汇集索引/聚簇索引,叶子节点包含了完整的数据记录,InnoDB的主键索引就是一个汇集索引,他的索引和数据是分开的在两个文件,MYISAM的是非汇集索引,索引和数据是分开存储的。InnoDB的主键索引咱们叫作汇集索引。

 

为何InnoDB表必须有主键,而且推荐使用整型的自增主键?

咱们看一下这个问题为何InnoDB表必须有主键,而且推荐使用整型的自增主键?

为甚innoDB表建议要有自增的主键,尽可能建主键,建整形自增的?其实很简单,设计如此,mysql设计的就是innoDB把你的数据和主键索引用B+Tree来组织的,没有主键他的数据就没有一个结构来存储。

 

建innoDB表的时候没有建主键,表也能建成功,为何?

不建主键不表明没有主键,没有建主键innodb会帮你选一个字段,一个能够标识惟一的字段,选为默认字段,若是这个字段惟一的话,不重复,可一键惟一索引的话,就会做为相似于惟一索引,用这个字段来做为惟一索引来维护整个表的数据。若是没有,mysql会生成一个惟一的列,相似于rowid,只不过你看不到,他会用生成的这个惟一列,维护B+Tree的结构,查数据的时候仍是用B+Tree的结构去查找。

 

为何推荐整形呢?

咱们想象一下查找过程,是把节点load到内存而后在内存里去比较大小,也就是在查找的过程当中要不断的去进行数据的比对。假设UUID,既不自增也不是整形。问一下,是整形的1<2比较的效率高仍是字符串的“abc”和“abe”比较的效率高呢?显然是前者,由于字符串的比较是转换成ASICI一位一位的比,若是最后一位不同,比到最后才比较出大小,就比整形比较慢多了,存储空间来讲,整形更小。索引越节约资源越好。

 

为何是自增的呢?

咱们能够看一下B-Tree的叶子节点之间是没有指针的,B+Tree优化后增长了叶子节点之间的指针,若是咱们遍历数据,从当前节点遍历完以后,就能够根据节点间的指针快速找到下一个节点去遍历。讲到这,穿插一下B+Tree为何要比B-Tree多一个节点间指针呢?那就讲一下索引的另外一种数据结构就是hash。

 

HASH索引

99.99的状况都是用B+Tree,也有些状况用hash。假设咱们的索引选的是hash的数据结构,每插入一个元素会把咱们的索引字段作一次hash计算,把运算的到的结果值和这一行的所在磁盘地址作一个映射。

 

对索引元素的值作一次hash运算就能够在hash映射表里快速找到这一行的磁盘文件地址,通过一次hash就能够快速定位到索引所在行的磁盘文件地址,hash这么快,表有一亿个数据按这种算法,那也就可能经历一次hash运算就能够快速找到某页任意一行数据元素的所在的磁盘文件地址,那比B+Tree快的多啊!就是快的多,为啥99.99的都是B+Tree不是hash呢?

 

hash的等值查询比B+Tree快,上亿依然很快,为啥很快却不使用?最主要的缘由是什么?由于若是使用范围查找,hash就没有用武之地了,范围查找也是很经常使用的吧,因此基本就不怎么用hash这种数据结构。那B+Tree就很好的支撑范围查找吗?

 

是,B+Tree能够很好的支撑。

看一下这个B+Tree的结构

 

image.png

 

刚才咱们说了B+Tree的任一叶子节点内部是从左到右都是递增的,且节点之间有一个指针(双向的,图不标准), 

假设咱们查大于20的记录,mysql内部是怎么查找的?先从根节点,定位到大于20的元素,而后依次从左到右找到30,而后这个节点遍历完了,就能够根据指针找到下一个节点的位置,由于B+Tree的特色,后面的元素全都大于20,就这样顺藤摸瓜把后面的元素全弄出来。

 

那B-Tree没有这个指针的话查找大于20 的元素那得多麻烦,先找出第一个节点中大于20的所有元素,由于还有别的节点,因此又要从根节点去遍历找下一个叶子节点,是否是很是慢。没有这个指针每次都要从根节点开始查找而后合并,那是很是慢的。

 

为何非主键索引结构叶子节点存储的是主键值?

为了一致性和节省存储空间。已经维护了一套主键索引+数据的B+Tree结构,若是再有其余的非主键索引的话,索引的叶子节点存储的是主键,这是为了节省空间,由于继续存数据的话,那就会致使一份数据存了多份,空间占用就会翻倍。另外一方面也是一致性的考虑,都经过主键索引来找到最终的数据,避免维护多份数据致使不一致的状况。


 

联合索引

尽可能建联合索引,少建单值索引 。刚讲的都是单值索引

 

联合索引的底层数据结构是什么样的?

 

image.png

 

多个列逐个字段去比较,(a,b,c)

 

多个索引有多个B+树结构,非主键索引叶子节点存储的不是数据,而是主键(一致性和节省空间)

 


手机端可扫码关注公众号,方便阅读

 

 

 

 

相关文章
相关标签/搜索