MySQL的索引为何用B+Tree?InnoDB的数据存储文件和MyISAM的有何不一样?

前言

这篇文章的题目,是我真实在面试过程当中遇到的问题,某互联网众筹公司在考察面试者MySQL相关知识的第一个问题,我当时仍是比较懵的,没想到这年轻人不讲武德,不按套路出牌,通常的问MySQL的相关知识的时候,不都是问索引优化以及索引失效等相关问题吗?怎么还出来了,存储文件的不一样?哪怕考察个MVCC机制也行啊。因此此次我就好好总结总结这部分知识点。mysql

为何须要创建索引

首先,咱们都知道创建索引的目的是为了提升查询速度,那么为何有了索引就能提升查询速度呢?
咱们来看一下,一个索引的示意图。

若是我有一个SQL语句是:select * from Table where id = 15 那么在没有索引的状况下实际上是会进行全表扫描的,就是挨个去找,直到找到id=15的这条记录,时间复杂度是O(n);面试

若是在有索引的状况下去进行查询呢。首先会根据id=15,在索引值里面进行二分查找,二分查找的效率是很高的,它的时间复杂度是O(logn);sql

这就是索引为何能提升查询效率了,可是索引数据的量也是比较大的,因此通常并非存储在内存中的,都是直接存储在磁盘中的,因此对磁盘中的文件内容进行读取,免不了要进行磁盘IO。shell

MySQL的索引为何使用B+Tree

上面咱们也说了,索引数据通常是存储在磁盘中的,可是计算数据都是要在内存中进行的,若是索引文件很大的话,并不能一次都加载进内存,因此在使用索引进行数据查找的时候是会进行屡次磁盘IO,将索引数据分批的加载到内存中,所以一个好的索引的数据结构,在获得正确的结果前提下,必定是磁盘IO次数最少的。数据库

Hash类型

目前MySQL实际上是有两种索引数据类型能够选择的,一个是BTree(实际是B+Tree)、一个Hash。服务器

可是为何在实际的使用过程当中,基本上大部分都是选择BTree呢?微信

由于若是使用Hash类型的索引,MySQL在建立索引的时候,会对索引数据进行一次Hash运算,这样根据Hash值就能快速的定位到磁盘指针了,就算数据量很大,也能快速精准的定位到数据。数据结构

  • 可是像select * from Table where id > 15这种范围查询,Hash类型的索引就搞不定了,对这种范围查询,会直接全表扫描,另外Hash类型的索引也搞不定排序。
  • 还有就是虽然MySQL底层作了一系列的处理,但仍是不能彻底的保证,不产生Hash碰撞。

二叉树

那MySQL为何没有二叉树做为它的索引数据结构呢?咱们都知道,二叉树是经过二分查找来进行定位数据的,因此效果仍是不错的,时间复杂度是O(logn);
二叉树
可是二叉树有个问题,就是在特殊状况下,它会退化成一根棍子,也就是一个单向链表。这个时候,它的时间复杂度就会退化成O(n);
二叉树退化成链表
因此当咱们要查询id=50的记录时,其实和全表扫描是同样的了。因此由于存在这种状况,二叉树不适合做为索引的数据结构。性能

平衡二叉树

那么既然二叉树,在特殊状况下会退化成链表,那么平衡二叉树为何不能够呢?学习

平衡二叉树的子节点高度差不能超过1,像下图中的二叉树,关键字为15的节点,它的左子节点高度为0,右子节点高度为1,高度差不超过1,因此下面这棵树是一棵平衡二叉树。
平衡二叉树
由于能保持平衡,因此它的查询时间复杂度为O(logN),至于怎么保持平衡的,主要是作一些左旋,右旋等,具体保持平衡的细节不是本文主要内容,想了解的可自行搜索。

用这个数据结构来作MySQL的索引会有 什么问题呢?

  • 磁盘IO过多:在MySQL当中,一次IO操做只读取一个节点,那么一个节点如果最多就两个子节点的话,那么就只有这两个子节点的查询范围,因此要精确到具体的数据时,就须要进行屡次读取,若是树很是深的话,那么将会进行大量的磁盘IO。性能天然降低了。
  • 空间利用率低:对于平衡二叉树来讲,每一个节点值保存一个关键字,一个数据区,两个子节点的指针。这样致使了,一次辛辛苦苦的IO操做就只加载这么点数据,实在是有点杀鸡用牛刀了。
  • 查询效果不稳定:若是在一个高度很深的平衡二叉树中,如果查询的数据正好是根节点,那么就会很快的查到,如果查询的数据正好是叶子节点,那么会进行屡次磁盘IO后才能返回,响应时间有可能和根节点的不在一个数量级上。

虽说二叉树解决的平衡的问题,可是也带来了新的问题,那就是因为它自己树的深度的,会形成一系列的效率问题。

那么为了解决平衡二叉树的这类问题,平衡多叉树(Balance Tree)就成为了更好的选择。

平衡多叉树(Balance Tree--B-Tree)

B-Tree的意思是平衡多叉树,通常B-Tree中的一个节点有多少个子节点,咱们就称为多少阶的B-Tree。一般用m表示阶数,当m为2的时候,就是平衡二叉树。

一棵B-Tree的每一个节点上最多能有m-1个关键字,最少要存放Math.ceil(m/2)-1个关键字,全部的叶子节点都在同一层。以下图就是一个4阶的B-Tree。
在这里插入图片描述
那么咱们看一下B-Tree是如何进行查找数据的

  • 如果查询id=7的数据,先将关键字20的节点加载进内存,判断出7比20小;
  • 那么加载第一个子节点,若查询的数据等于12或17则直接返回,不等于就继续向下找,发现7小于12;
  • 那么继续加载第一个子节点中去,找到7以后,直接将7下面的data数据返回。

这样整个操做其实进行了3次IO操做,但实际上通常的B-Tree每层都是有不少分支(一般都大于100)。

MySQL为了能更好的利用磁盘的IO能力,将操做页的大小设置为了16K,即每一个节点的大小为16K。若是每一个节点中的关键字都是int类型的,那么就是4个字节,若数据区的大小为8个字节,节点指针再占4个字节,那么B-Tree的每一个节点中能够保存的关键字个数为:(16*1000) / (4+8+4)=1000,每一个节点最多可存储1000个关键字,每个节点最多能够有1001个分支节点。

这样在查询索引数据的时候,一次磁盘IO操做能够将1000个关键字,读取到内存中进行计算,B-Tree的一次磁盘IO的操做,顶上平衡二叉数据的N次磁盘IO操做了。

要注意的是B-Tree为了保证数据的平衡,会作一系列的操做,这个保持平衡的过程比较耗时间,因此在建立索引的时候,要选择合适的字段,而且不要过多的建立索引,建立索引过多的话,在更新数据的时候,更新索引的过程也比较耗时。

还有就是不要选择低区分度字段值做为索引,例如性别字段,总共就两个值,那么就有可能会形成B-Tree的深度过大,索引效率下降。

B+Tree

B-Tree已经很好的解决平衡二叉树的问题了,而且也能保证查询效率了,那么为何会有B+Tree呢?

咱们先来B+Tree是什么样子的。

B+Tree是B-Tree的变种,B+Tree的每一个节点关键字和m阶的公式关系和B-Tree的不同了。

首先每一个节点的子节点数量和每一个节点可存储的关键字比例是1:1,其次就是查询数据的时候采用的是左闭合区间进行查询,还有就是分支节点中没有数据了只保存关键字和子节点指向,数据都存储在叶子节点。
在这里插入图片描述
那么来看一下在B+Tree中是如何进行数据查询的。

例如:

  • 如今要查询id=2的数据,那么会先将根节点取出,加载到内存中,发现id=2存在于根节点,由于是左闭合区间存储数据,因此id<=2的都在根节点的第一个子节点上;
  • 那么取出第一个子节点,加载到内存中,发现当前节点存在id=2的关键字,而且已经到了叶子节点了,那么直接取出叶子节点中的数据返回。

如今来看一下B-Tree和B+Tree的区别

  • B+Tree的查询采用的左闭合区间,这样能更好的支持了自增索引的查询效果,因此通常在建立主键的时候一般都是自增的。这一点和B-Tree是不同的。
  • B+Tree中的根节点和分支节点上是不保存数据的,关键字相关的数据只保存在叶子节点上,这样保证了查询效果的稳定,任何查询都要走到叶子节点才能获取数据。而B-Tree在分支节点中保存了数据,如果命中关键字则直接返回数据。
  • B+Tree的叶子节点是顺序排列的,而且相邻的两个叶子节点中具备顺序引用的关系,这样能更好的支持了范围查询。而B-Tree是没有这个顺序关系的。

MySQL的索引为何选择了B+Tree

通过上面的层层分析,如今咱们能够总结一下MySQL为何选择了B+Tree做为它索引的数据结构呢。

  1. 首先和平衡二叉树相比,B+Tree的深度更低,节点保存关键字更多,磁盘IO次数更少,查询计算效率更好。

  2. B+Tree的全局扫描能力更强,如果想根据索引数据对数据表进行全局扫描,B-Tree会将整棵树进行扫描,而后逐层遍历。而B+Tree呢,只须要遍历叶子节点便可,由于叶子节点之间存在顺序引用的关系。

  3. B+Tree的磁盘IO读写能力更强,由于B+Tree的每一个分支节点上只保存了关键字,这样每次磁盘IO在读写的时候,一页16K数据量能够存储更多的关键字了,每一个节点上保存的关键字也比B-Tree更多了。这样B+Tree的一次磁盘IO加载的数据比B-Tree的多不少了。

  4. B+Tree数据结构中有自然的排序能力,比其余数据结构排序能力更强并且排序时,是经过分支节点来进行的,如果须要将分支节点加载到内存中排序,一次加载的数据更多。

  5. B+Tree的查询效果更稳定,由于全部的查询都是须要扫描到叶子节点才将数据返回的。效果只是稳定而不必定是最优,如果直接查询B-Tree的根节点数据,那么B-Tree只须要一次磁盘IO就能够直接将数据返回,反而是效果最优。

通过以上几点的分析,MySQL最终选择了B+Tree做为了它的索引的数据结构。

InnDB的数据存储文件和MyISAM的有何不一样?

上面总结了MySQL的索引的数据结构,此次就能够说第二个问题了,由于这个问题其实和MySQL的索引仍是有必定的关系的。
下面来看一下,先找到服务器桑MySQL存储数据的目录:
登陆MySQL,打开MySQL的命令行界面:输入show variables like '%datadir%';,就能看到存储数据的目录了。
个人服务器中MySQL的存储数据的目录是在:

/var/lib/mysql/

进入到这个目录里后,能看到全部数据库的目录,新建一个study_test的数据库。
而后就进入

/var/lib/mysql/study_test

这个目录下,目前就只有一个文件,这个文件是用来记录建立数据库时配置的字符集的内容。

-rw-r----- 1 mysql mysql     60 1月  31 10:28 db.opt

如今新建两个表,第一个表的引擎类型选择InnoDB,第二个表的引擎类型选择MyISAM。

student_innodb

CREATE TABLE `student_innodb` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`) USING BTREE COMMENT 'name索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='innodb引擎表';

student_myisam

CREATE TABLE `student_myisam` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`) USING BTREE COMMENT 'name索引'
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='myISAM引擎类型表';

将两个表建立完成后,咱们再进入到/var/lib/mysql/study_test看一下:

-rw-r----- 1 mysql mysql     60 1月  31 10:28 db.opt
-rw-r----- 1 mysql mysql   8650 1月  31 10:41 student_innodb.frm
-rw-r----- 1 mysql mysql 114688 1月  31 10:41 student_innodb.ibd
-rw-r----- 1 mysql mysql   8650 1月  31 10:58 student_myisam.frm
-rw-r----- 1 mysql mysql      0 1月  31 10:58 student_myisam.MYD
-rw-r----- 1 mysql mysql   1024 1月  31 10:58 student_myisam.MYI

经过目录中的文件可看到建立表以后多了几个文件,这样也看出来了,InnoDB引擎类型的表和MyISAM引擎类型的表的文件差别。

这几个文件每一个都是有本身的做用:

  • InnoDB引擎的表文件,一共有两个:
    • *.frm 这类文件是表的定义文件。
    • *.ibd 这类文件是数据和索引存储文件。表数据和索引汇集存储,经过索引能直接查询到数据。
  • MyIASM引擎的表文件,一共有三个:
    • *.frm 这类文件是表的定义文件。
    • *.MYD 这类文件是表数据文件,表中的全部数据都保存在此文件中。
    • *.MYI 这类文件是表的索引文件,MyISAM存储引擎的索引数据单独存储。

MyISAM数据存储引擎,索引与数据的存储结构

MyISAM存储引擎在存储索引的时候,是将索引数据单独存储,而且索引的B+Tree最终指向的是数据存在的物理地址,而不是具体的数据。而后再根据物理地址去数据文件(*.MYD)中找到具体的数据。

以下图所示:

MyISAM索引存储结构
那么当存在多个索引时,多个索引都指向相同的物理地址。
以下图所示:
MyISAM的多个索引
经过这个结构,咱们能够看出来,MyISAM的存储引擎的索引都是同级别的,主键和非主键索引结构和查询方式彻底同样。

InnoDB数据存储引擎,索引与数据的存储结构

首先InnoDB的索引分为聚簇索引和非聚簇索引,聚簇索引即保存关键字又保存数据,在B+Tree的每一个分支节点上保存关键字,叶子节点上保存数据。
聚簇”的意思是数据行被按照必定顺序一个个紧密地排列在一块儿存储。一个表只能有一个聚簇索引,由于在一个表中数据的存放方式只有一种,通常是主键做为聚簇索引,若是没有主键,InnoDB会默认生成一个隐藏的列做为主键。

以下图所示:
InnoDB的聚簇索引
非聚簇索引,又称为二级索引,虽然也是在B+Tree的每一个分支节点上保存关键字,可是叶子节点不是保存的数据,而是保存的主键值。经过二级索引去查询数据会先查询到数据对应的主键,而后再根据主键查询到具体的数据行。

以下图所示:
InnoDB非聚簇索引
因为非聚簇索引的设计结构,致使了,非聚簇索引在查询的时候要进行两次索引检索,这样设计的好处,能够保证了一旦发生数据迁移的时候,只须要更新主键索引便可,非聚簇索引并不用动,并且也规避了像MyISAM的索引那样存储物理地址,在数据迁移的时候的须要从新维护全部索引的问题。

总结

此次把MySQL的索引的数据结构,以及文件存储结构,总结清楚了,后面在实际的工做过程当中,设计索引的时候可以考虑的更全了,经过了解了索引的数据结构,也能让本身在实际写SQL的时候,能考虑到哪些状况走索引哪些不走索引了。

  • MySQL使用B+Tree做为索引的数据结构,由于B+Tree的深度低,节点保存的关键字多,磁盘IO次数少,从而保证了查询效率更高。
  • B+Tree可以保证MySQL不管是主键索引仍是非主键索引的查询效果都是稳定的,每次都要查询到叶子节点才能返回数据,B+Tree的叶子节点的深度是同样的,并且为了更好的支持自增主键,B+Tree的查询节点范围是左闭合右开放。
  • MySQL的MyISAM存储引擎,表数据索引数据是分别放到两个文件中进行存储的,因为它自己的索引的B+Tree的叶子节点指向的表数据所在的磁盘地址,并且索引没有主键和非主键之分,因此分开存储,可以更好的统一管理索引;
  • MySQL的InnoDB存储引擎,表数据索引数据是存储在一个文件中的,由于InnoDB的聚簇索引的叶子节点指向的具体的数据行,并且为了保证查询效果的稳定,InnoDB表中必需要有一个聚簇索引,二级索引在进行索引检索时,会先经过二级索引检索到数据的主键值,再根据主键去聚簇索引中检索到具体的数据。

欢迎关注我,一块儿学习,一块儿进步!

微信公众号:Jimoer

相关文章
相关标签/搜索