简单了解索引

首先,索引(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 索引的更多相关文章

  1. 重新学习MySQL数据库2:『浅入浅出』MySQL 和 InnoDB

    重新学习Mysql数据库2:『浅入浅出』MySQL 和 InnoDB 作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.P ...

  2. 『浅入浅出』MySQL 和 InnoDB

    作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.PostgreSQL,但是一直以来也没有对数据库有一个非常清晰并且成体系 ...

  3. 『浅入深出』MySQL 中事务的实现

    在关系型数据库中,事务的重要性不言而喻,只要对数据库稍有了解的人都知道事务具有 ACID 四个基本属性,而我们不知道的可能就是数据库是如何实现这四个属性的:在这篇文章中,我们将对事务的实现进行分析,尝 ...

  4. 浅入深出之Java集合框架(上)

    Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  5. 浅入深出之Java集合框架(下)

    Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...

  6. Spring浅入浅出——不吹牛逼不装逼

    Spring浅入浅出——不吹牛逼不装逼 前言: 今天决定要开始总结框架了,虽然以前总结过两篇,但是思维是变化的,而且也没有什么规定说总结过的东西就不能再总结了,是吧.这次总结我命名为浅入浅出,主要在于 ...

  7. Spring注解浅入浅出——不吹牛逼不装逼

    Spring注解浅入浅出——不吹牛逼不装逼 前情提要 上文书咱们说了<Spring浅入浅出>,对Spring的核心思想看过上篇的朋友应该已经掌握了,此篇用上篇铺垫,引入注解,继续深入学习. ...

  8. Spring的数据库编程浅入浅出——不吹牛逼不装逼

    Spring的数据库编程浅入浅出——不吹牛逼不装逼 前言 上文书我写了Spring的核心部分控制反转和依赖注入,后来又衔接了注解,在这后面本来是应该写Spring AOP的,但我觉得对于初学者来说,这 ...

  9. Spring MVC浅入浅出——不吹牛逼不装逼

    Spring MVC浅入浅出——不吹牛逼不装逼 前言 上文书说了Spring相关的知识,对Spring来了个浅入浅出,大家应该了解到,Spring在三层架构中主做Service层,那还有Web层,也就 ...

  10. 浅入浅出EmguCv(三)EmguCv打开指定视频

    打开视频的思路跟打开图片的思路是一样的,只不过视频是由一帧帧图片组成,因此,打开视频的处理程序有一个连续的获取图片并逐帧显示的处理过程.GUI同<浅入浅出EmguCv(二)EmguCv打开指定图 ...

随机推荐

  1. GCD and LCM HDU - 4497

    题目链接:https://vjudge.net/problem/HDU-4497 题意:求有多少组(x,y,z)满足gcd(x,y,z)=a,lcm(x,y,z)=b. 思路:对于x,y,z都可以写成 ...

  2. c++ 反汇编 除法优化

    接上篇:<C++反汇编与逆向分析技术揭秘>--算术运算和赋值 printf("argc / 4 = %d\n", argc / 4); printf("arg ...

  3. visualvm工具远程对linux服务器上的JVM虚拟机进行监控与调优

    文/朱季谦 最近在做了一些JVM监控与调优的事情,算是第一次实践,还比较陌生,故而先把这一次经验简单记下笔记,这样,对后面学习调优方面时,不至于又想不起来了.本文档主要总结在window本地环境远程对 ...

  4. vue 快速入门 系列 —— 侦测数据的变化 - [vue 源码分析]

    其他章节请看: vue 快速入门 系列 侦测数据的变化 - [vue 源码分析] 本文将 vue 中与数据侦测相关的源码摘了出来,配合上文(侦测数据的变化 - [基本实现]) 一起来分析一下 vue ...

  5. 提高Python的性能

    01 使用哈希表的数据结构   如果在程序中遇到大量搜索操作时,并且数据中没有重复项,则可以使用查找而不是循环.举例如下: items = ['a', 'b',..,'100m'] #1000s of ...

  6. 02-MySQL主要配置文件

    一.二进制日志log-bin 作用:主从复制 二.错误日志 log-err 默认关闭,记录严重的警告和错误信息,每次启动和关闭的详细信息 三.慢查询日志log 默认关闭,记录查询的sql语句,如果开启 ...

  7. 【笔记】《Redis设计与实现》chapter18 发布与订阅

    chapter18 发布与订阅 客户端订阅频道. 客户端向频道发送消息, 消息被传递至各个订阅者. 匹配模式 客户端订阅模式. 客户端向频道发送消息, 消息被传递给正在订阅匹配模式的订阅者. 另一个模 ...

  8. ACCESS常见错误场景

    ACCESS常见错误场景 今天用access时发现好多报错的地方,emmm,比MySQL麻烦好多,有些甚至还要自己去配置环境 不吐槽了,进入正题: 报错场景一:您尝试执行不包含指定聚合函数的查询 第一 ...

  9. 2.Python进程间的通信之队列(Queue)和生产者消费者模型

    一.队列 1.1 概念介绍-----multiprocess.Queue 创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递. Queue([maxsize] ...

  10. isAssignableFrom与instanceof

    isAssignableFrom()方法与instanceof关键字的区别总结为以下两个点: isAssignableFrom()方法是从类继承的角度去判断,instanceof关键字是从实例继承的角 ...