浅入浅出 MySQL 索引
简单了解索引
首先,索引(Index)是什么?如果我直接告诉你索引是数据库管理系统中的一个有序的数据结构,你可能会有点懵逼。

为了避免这种情况,我打算举几个例子来帮助你更容易的认识索引。
我们查询字典的时候可以根据字的部首、笔画来查找到对应的字,这样可以快速的找到对应的字所在页,在字典开头那玩意就叫索引
还有一本书的目录,可以帮我们快速的跳到不同的章节,此时这里的目录也是索引
甚至,景区的地图,会告诉你你现在在哪里,其他景点在哪儿,这个地图从某些方面来说也是索引
再结合开篇较专业的解释,你可能就能够理解索引是什么了。

为什么需要索引
了解了索引的概念,我们就需要知道为什么我们需要索引?从刚刚的例子可以看出来,索引的存在的目的就是:

字典中的索引帮助我们快速的找到对应的字
书的目录帮助我们快速的跳到我们需要看的章节
景区的地图帮助我们快速的找到想要去的景区的路
在数据库中,索引可以帮助我们快速的查询到对应的数据行,从而顺利的取出所有列的数据。这个过程必须要快,对于现在的 Web 应用来说,DB 如果响应慢,将会直接影响到整个请求的响应时间,而这对用户体验来说是灾难性的。
对于点个按钮,等个好几秒才有返回,那么之后用户大概率是不会再使用你开发的应用了。
MySQL中的索引
首先,MySQL 和索引其实没有直接的关系。索引其实是 MySQL 中使用的存储引擎 InnoDB 中的概念。在 InnoDB 中,索引分为:
聚簇索引 非聚簇索引
对于聚簇索引,是 InnoDB 根据主键(Primary Key)构建的索引。你可以暂时理解为 key 为主键,value 则是整行数据。并且一张表只能有一个聚簇索引。

当然,你可以不定义主键。但是正常情况下我们都会创建一个单调递增的主键,或者是通过统一的 ID 生成算法生成。如果没有定义任何主键,InnoDB 会有自己的兜底策略。InnoDB 会选择我们定义的第一个所有值的都不为空的唯一索引作为聚簇索引。

不过实际的生产环境中,的确会有这样的 Corner Case。InnoDB 还有一个究极兜底,如果连仅剩的唯一索引也不符合要求,InnoDB 会自己创建一个隐藏的6个字节的主键 RowID,然后根据这个隐藏的主键来生成聚簇索引。
而对于非聚簇索引,是根据指定的列创建的索引,也叫二级索引(Secondary Index),一张表最多可以创建64个二级索引。key 为创建二级索引的列的值,value 为主键。换句话说,如果通过非聚簇索引查询,最终只能得到索引列本身的值 + 主键的值,如果想要获取到完整的列数据,还需要根据得到的主键去聚簇索引中再查询一次,这个过程叫回表。
这里说明一下,现在有很多的博客说,MySQL 使用 InnoDB 时,一张表最多只能创建 16 个索引,首先这是错的,明显是从其他的地方直接抄过来的,自己没有去做任何的验证。
在 MySQL 的官方文章中,明确的说明了,一张表最多可以创建 64 个非聚簇索引,而且创建非聚簇索引时,列的数量不能超过16个。
注意,是创建非聚簇索引的列不能超过16个!

这也顺便提一下题外话,所谓的技术严谨,什么叫严谨?对你通过其他渠道获取到的知识,它最多叫作者的观点,我们持一种怀疑态度,并想办法自己去求证。求证后,它才会变成事实。
而不是对某些名词死记硬背,现在的新玩意层出不穷,但当你溯其根源,你会发现就那么回事。
索引底层原理
前面提到了 InnoDB 中索引的类型,简单的了解了其分类和区别,那 InnoDB 中的索引是如何做到加速查询的呢?其底层的原理是啥呢?InnoDB 中的索引的底层结构为 B+ 树,是B树的一个变种。

先给大家看看B+树到底长个什么鸟样,下图是一颗存储了数字「1-7」的B+树。

可以看到,B+树中,每个节点可以有多个子节点,而像我们平常熟悉的二叉树中,每个节点最多只能有2个。并且,B+树中,节点的存储数据是有序的,而有序的数据结构就可以让我们进行快速的精确匹配和范围查询。而且B+树中的叶子结点之间有指向下一个节点的指针,而B树中的叶子节点是没有的。
在 MySQL InnoDB 的实际实现中,页节点之间其实是个双链表,存储了分别指向上一个、下一个节点的指针
下图是包含了整数「1-7」的B树,这个图应该会帮助你加深对两者区别的理解。

并且,在B+树中,除了叶子节点存储了真实的数据之外,其余的节点都只存储了指向下一节点的指针。换句话说,数据全部都在叶子节点上。而在B树中,所有的节点都可以存储数据,这是一个最主要的区别。
知道了B树和B+树的基础结构长啥样之后,我们需要再深入了解 InnoDB 是如何利用B+树来存储数据的。首先,MySQL 并不会把数据存储在内存中,内存只是作为运行时的一种优化,关于 InnoDB 内存架构相关的东西,之前已经写了一篇文章,感兴趣的可以先去看看。
InnoDB 会将数据存储在磁盘上,而当我们查询数据的时候,OS 会将存储在磁盘上的数据一页一页的加载到内存里。这里的页是 OS 管理内存的一种方式,当其加载数据到内存时,会将某个磁盘块上的数据按照页的大小加载。在这里,你可以理解为B树中每个节点就是一个磁盘块。
那既然B树和B+树在查找的时候都需要进行 I/O 操作将需要的节点加载到内存,B+树相对于B树的优势到底在哪儿?

个人认为主要有三点。
一是B+树能够减少 I/O 的次数。为啥呢?凭啥数据结构长的差不多,B+树就能够减少 I/O 的次数?之前说到,单个节点就代表了一个磁盘块,而单个磁盘块的大小是固定的。B+树仅有叶子结点才存储值,相对于所有节点都存完整数据的B树而言,B+树中单个磁盘块能够容纳更多的数据。

单个磁盘块,容量固定的前提下,存储的元素大小越小,则能够存储的元素的数量就会更多。换句话说,一次 I/O 能够把更多的数据加载进内存,而这些多加载的元素很可能是你会用到的,而这就一定程度上能减少 I/O 的次数。
除此之外,单个节点能够存储的元素增多了,还能够起到减少树的高度的作用。
二是查询效率更加稳定。什么叫更稳定呢?那就在数据量相同的情况下,不会因为你查询的数据 ID 不同而造成查询所耗费时间大相径庭,换句话说,这次请求可能花了10ms,下一次同样的请求啪的一下花了20ms,这就让人很不能接受,合着接口的性能还要看你数据库的心情?
那为什么说使用B+树就能够做到查询效率稳定呢?因为B+树非叶子结点不会存储数据,所以如果要获取到最终的数据,必然会查到叶子结点,换句话说,每次查询的 I/O 次数是相同的。而B树由于所有节点均可存储数据,有的数据可能1次 I/O 就查询到了,而有的则需要查询到叶子结点才找到数据,而这就会带来查询效率的不稳定。
三是能够更好的支持范围查询。那B树为啥就不能很好的支持呢?让我们回到B树这张图。

假设我们需要查询 [3, 5] 这个区间内的数据,会经历什么呢?不废话,直接把图给出来。

可以看到,如果到叶子结点仍然没有查询到完整的数据,会重新返回到根结点再次进行遍历。而反观 B+ 树,当找到了叶子结点之后就可以通过叶子结点之间的指针直接进行链表遍历,可以大大的提升范围查询的效率。
知道了这点之后,举一反三就能够知道,为什么 InnoDB 不使用 Hash 在做底层的数据结构了。即使查询时 Hash 的时间复杂度甚至能做到 O(1)
最后聊聊 I/O
全篇提到了很多次 I/O,以及在 MySQL 的索引设计中,需要尽量的减少 I/O 次数,为啥呢?是因为 I/O 很昂贵。当我们执行一次 I/O,到底发生了什么?
本来像详细讲讲磁盘结构的,但是看了一眼篇幅,已经快超了,所以这里就简单的聊聊就好
机械硬盘中,一次 I/O 操作,由三个步骤组成:
首先需要寻道,寻道是指磁盘的磁头移动道磁盘上的磁道上面,这个时间一般在3-15ms内。
然后是旋转,磁盘会将存储对应数据的盘片旋转至磁头下方,这又花掉2ms左右,具体的时延与磁盘的转速有关。
最后是数据传输。
一波操作下来,花费就在10ms左右。不要以为10ms还好...对比于SSD(固态硬盘)和内存的微秒、纳秒来说,简直有着天壤之别。

这也是为啥在 MySQL 中,随机 I/O 对其查询的性能影响很大的原因。
好了以上就是本篇博客的全部内容了,欢迎微信搜索关注【SH的全栈笔记】,回复【队列】获取MQ学习资料,包含基础概念解析和RocketMQ详细的源码解析,持续更新中。
如果你觉得这篇文章对你有帮助,还麻烦点个赞,关个注,分个享,留个言。

浅入浅出 MySQL 索引的更多相关文章
- 重新学习MySQL数据库2:『浅入浅出』MySQL 和 InnoDB
重新学习Mysql数据库2:『浅入浅出』MySQL 和 InnoDB 作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.P ...
- 『浅入浅出』MySQL 和 InnoDB
作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.PostgreSQL,但是一直以来也没有对数据库有一个非常清晰并且成体系 ...
- 『浅入深出』MySQL 中事务的实现
在关系型数据库中,事务的重要性不言而喻,只要对数据库稍有了解的人都知道事务具有 ACID 四个基本属性,而我们不知道的可能就是数据库是如何实现这四个属性的:在这篇文章中,我们将对事务的实现进行分析,尝 ...
- 浅入深出之Java集合框架(上)
Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- 浅入深出之Java集合框架(下)
Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...
- Spring浅入浅出——不吹牛逼不装逼
Spring浅入浅出——不吹牛逼不装逼 前言: 今天决定要开始总结框架了,虽然以前总结过两篇,但是思维是变化的,而且也没有什么规定说总结过的东西就不能再总结了,是吧.这次总结我命名为浅入浅出,主要在于 ...
- Spring注解浅入浅出——不吹牛逼不装逼
Spring注解浅入浅出——不吹牛逼不装逼 前情提要 上文书咱们说了<Spring浅入浅出>,对Spring的核心思想看过上篇的朋友应该已经掌握了,此篇用上篇铺垫,引入注解,继续深入学习. ...
- Spring的数据库编程浅入浅出——不吹牛逼不装逼
Spring的数据库编程浅入浅出——不吹牛逼不装逼 前言 上文书我写了Spring的核心部分控制反转和依赖注入,后来又衔接了注解,在这后面本来是应该写Spring AOP的,但我觉得对于初学者来说,这 ...
- Spring MVC浅入浅出——不吹牛逼不装逼
Spring MVC浅入浅出——不吹牛逼不装逼 前言 上文书说了Spring相关的知识,对Spring来了个浅入浅出,大家应该了解到,Spring在三层架构中主做Service层,那还有Web层,也就 ...
- 浅入浅出EmguCv(三)EmguCv打开指定视频
打开视频的思路跟打开图片的思路是一样的,只不过视频是由一帧帧图片组成,因此,打开视频的处理程序有一个连续的获取图片并逐帧显示的处理过程.GUI同<浅入浅出EmguCv(二)EmguCv打开指定图 ...
随机推荐
- 《Selenium自动化测试实战:基于Python》之 Python与Selenium环境的搭建
第2章 Python与Selenium环境的搭建 购买链接: 京东:https://item.jd.com/13123910.html 当当:http://product.dangdang.co ...
- P1308_统计单词数(JAVA语言)
题目描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给 ...
- 策略枚举:消除在项目里大批量使用if-else的正确姿势
文/朱季谦 想起刚开始接触JAVA编程的时候,若遇到大量流程判断语句,几乎满屏都是if-else语句,多得让自己都忘了哪里是头,哪里是尾,但是,纵然满屏是if-else,但彼时也没有觉得多别扭.等到编 ...
- java例题_14 该日期一年中的第几天问题
1 /*14 [程序 14 求日期] 2 题目:输入某年某月某日,判断这一天是这一年的第几天? 3 程序分析:以 3 月 5 日为例,应该先把前两个月的加起来,然后再加上 5 天即本年的第几天,特殊情 ...
- 获取本机外网ip
获取内网ip ifconfig eth0 | grep 'inet'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $2}' 获取公网ip ifc ...
- 【C++】 C++知识点总结
作者:李春港 出处:https://www.cnblogs.com/lcgbk/p/14643010.html 目录 前言 一.C++常用后缀 二.头文件 1.C++输入输出 2.在C++中使用C的库 ...
- Python是啥?为什么这么多职业人和学生就算报班也要学它?!
嗨,大家好 这里是汐仔 首先我们先来考究一下近几年的头条和新闻. 1.早在2018年python就已经被纳入高考之一了 2.Python加入全国计算机等级考试,从2018年九月起新增为大学计算机二级考 ...
- 「HTML+CSS」--自定义加载动画【016】
前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...
- linux下Mysql 8.0.19 编译安装
1 前言 linux下安装MySQL的方式有很多种,包括以仓库的方式安装(yum,apt,zypper),以包的方式安装(rpm,deb),以docker方式安装,从压缩包解压安装,从源码编译安装,这 ...
- Java8 Map computeIfAbsent方法说明
// 方法定义 default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { . ...