一文搞懂mysql索引底层逻辑,干货满满!

一、 什么是索引

二、 为什么要用索引

例如,我们通过查询语句查询一条记录:select * from table where Col2 = 85,如果没有索引的话,那么它将从第一行[1,35]开始找,一行一行的找,直到找到[6,85]这条数据,并且数据存放的位置也不规则,拿取一行记录就需要与磁盘进行一次交互,即IO读取,如果数据多,这种效率将会很低下,只要把这种交互次数控制在一定范围之内,那他的效率将会比一行行查找要高很多,如给col2加索引,来执行select * from table where Col2 = 85,通过二叉树接口,第一次我们查到的是35,85比35大,所以查找右子节点,查到85,与条件种的85为一条数据,所以,这里就只需要两次交互就可以查到。所以索引就诞生了。

三、 索引的数据结构

1.1、二叉树的特点:

1、每个节点最多有两个子树,所以二叉树不存在度大于2的节点(结点的度:结点拥有的 子树的数目。),可以没有子树或者一个子树。
2.左子树和右子树有顺序,次序不能任意颠倒。
3、二叉树支持动态的插⼊和查找,保证操作在O(height)时间,这就是完成了哈希表不便完成的⼯作,动态性。但是⼆叉树有可能出现worst-case,如果 输⼊序列已经排序,则时间复杂度为O(N)。为什么不用二叉树来作为索引,就是因为二叉树的worst-case,如果输入序列是排好序的,那么二叉树的结构就会变成如下图所示的特殊状态:

所以二叉书并不适合去做索引,遇到这种极端情况,就会导致有索引和无索引效果一样。

AVL树是严格的平衡二叉树,所有节点的左右子树高度差不能超过1;AVL树查找、插入和删除在平均和最坏情况下都是O(lgn)。AVL实现平衡的关键在于旋转操作:插入和删除可能破坏二叉树的平衡,此时需要通过一次或多次树旋转来重新平衡这个树。当插入数据时,最多只需要1次旋转(单旋转或双旋转);但是当删除数据时,会导致树失衡,AVL需要维护从被删除节点到根节点这条路径上所有节点的平衡,旋转的量级为O(lgn)。由于旋转的耗时,AVL树在删除数据时效率很低;在删除操作较多时,维护平衡所需的代价可能高于其带来的好处,因此AVL实际使用并不广泛。

与AVL树相比,红黑树;并不追求严格的平衡,而是大致的平衡:只是确保从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。从实现来看,红黑树最大的特点是每个节点都属于两种颜色(红色或黑色)之一,且节点颜色的划分需要满足特定的规则。在java8中的HashMap就是使用链表+红黑树。红黑树的缺点就是太高了,如下图所示:

当数据量特别大的时候,树的高度很高,假设你要查找的节点为当前树的叶子节点,那么要查找这个节点,至少要循环h(这棵树的高度)次,所以说,红黑树在这种情况下也并不适用。

Tree就是我们常说的B树,它是一种多路搜索树而非二叉树,使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度

在B树中,每个节点包含:

1、本结点所含关键字的个数;

2、指向父节点的指针

3、关键字

4、指向子节点的指针

对于一棵m阶B-tree,每个结点至多可以拥有m个子结点。各结点的关键字和可以拥有的子结点数都有限制,规定m阶B-tree中,根结点至少有2个子结点,除非根结点为叶子节点,相应的,根结点中关键字的个数为1~m-1;非根结点至少有m/2个子结点,相应的,关键字个数为[m/2]-1~m-1。

B-tree有以下特性:

1、关键字集合分布在整棵树中;

2、任何一个关键字出现且只出现在一个结点中; 所有索引元素不重复

4、其搜索性能等价于在关键字全集内做一次二分查找;

5、自动层次控制;

6、所有叶节点都在同一层,每个节点最多有m-1个key,并且以升序排列

叶节点具有相同的深度,叶节点的指针为空

由于限制了除根结点以外的非叶子结点,至少含有M/2个儿子,确保了结点的至少利用率,其最低搜索性能为:

所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;

由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并。

B树的查询:

B树是二叉排序树的扩展,二叉排序树是二路查找,B-树是多路查找。因为B-树节点内的关键字是有序的,在节点内查找的时候除了顺序查找之外,还可以用折半查找提高效率,B-树的具体查找步骤可以参照折半查找方法。

以查找42为例:

首先获取关键点的关键字进行比较,当前根节点关键字为30,42>30,所以找右子节点,拿到关键字39,45,39

B+树是一种树数据结构,通常用于数据库;操作系统文件系统中。B+树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。

B+树的

非叶子节点不存储data,只存储索引(冗余),可以放更多的索引,只有叶子节点才存储数据

叶子节点包含所有索引字段

叶子节点增加了一个指向相邻子节点的指针,它的最后一个数据会指向下一个叶子节点的第一个数据,形成一个有序链表的结构,提高区间访问的性能。

与B树相比它的不同体现在:

(1).如果非叶子节点包含n个关键码,则这个节点有n个子树。

(2).非叶子节点仅包含关键码信息,叶子节点包含关键码以及含有这个关键码的记录的指针。所以查找时,B+树必须到达叶子节点才会命中。

(3).叶子节点包含有兄弟叶子节点的指针,而且叶子节点的关键码值是有序的,有利于遍历。

(4).所有的非叶子节点可看成是索引部分(稀疏索引)

为什么说B+树比B树更适合实际应用中作为操作系统的文件索引和数据库索引

(1)B+树的磁盘读写代价更低

非叶子节点包含的信息更少,如果把同一节点的所有信息放在一个磁盘块中,则可以比B树放入更多的关键码。一次读入内存当中(读一个块)就能读入更多的关键码,所以降低了磁盘I/O总数。

(2)查询效率更加稳定

对任何关键字的查找都必须从根节点走到叶子节点,路径长度相同,所以对每条数据的查询效率相当,在存储相等的关键字上,B+树树的高度会更低。

(3)B树在提高磁盘I/O性能的同时并没有解决元素遍历效率低下的问题。而B+树因为叶子节点有链指针存在,所以遍历叶子节点即可以实现对整棵树的遍历。而在数据库中基于范围的查询是非常频繁的,B+树就能更好的支持。

四、存储引擎索引实现

数据存储引擎是形容数据库表层面的,而不是形容数据库的,我们点击表设计,在选项中证实这一问题。

MYISAM基于ISAM存储引擎,并对其进行扩展。它是在web、数据仓储和其他应用环境下最常用的存储引擎之一。MYISAM拥有较高的插入、查询速度,但不支持事务和外键。所以对事务完整性没有要求或者以SELECT、INSERT为主的应用基本上都可以使用这个引擎来创建表。 数据文件和索引文件可以放置在不同的目录,平均分布 IO,获得更快的速度。要指定索引文件和数据文件的路径,需要在创建表的时候通过 DATA DIRECTORY 和 INDEX DIRECTORY 语句指定,也就是说不同 MyISAM 表的索引文件和数据文件可以放置到不同的路径下。文件路径需要是绝对路径,并且具有访问权限。

MYISAM存储引擎的索引

1)MyISAM默认使用B+Tree索引,只把索引载入内存,存储的是数据的索引

2)MyISAM数据库中的数据是按照插入的顺序保存,在每个索引节点中保存对应的数据行的地址,理论上说主键索引和其他索引是一样的。

3)MYISAM的索引文件和数据文件是分离的(非聚集)

MYD文件存的是表的数据

MYI文件存的是表的索引

frm文件存的是表的结构

InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MYISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。但是由于其其他方面的优势,在5.5版本之后,MYSQL的默认引擎变成了InnoDB.

2.1InnoDB索引实现(聚集)

InnoDB表只有一个聚集索引

表数据文件本身就是按B+Tree组织的一个索引结构文件,聚集索引-叶子节点包含了完整的数据记录

InnoDB存储引擎存储数据库数据,一共有两个文件

frm文件:表的结构

ibd文件: 数据和索引存储文件。数据以主键进行聚集存储,把真正的数据保存到叶子节点中

因为在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的Key是数据库的主键,因此InnoDB表数据文件本身就是主索引。综上所述,InnoDB数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MYISAM可以没有,因为数据和索引是分开的),如果没有指定,那么Mysql系统会自动选择一个所有元素均不相等的列作为主键,如果不存在这种列,则Mysql自动为InnoDB表生成一个隐含字段作为主键,这个字段长度未6个字节,类型为长整型。

因为B+Tree再找数据的时候会去比较大小,整型数值比大小要相对简单和快速,且索引节点占的内存会更小。再有就是主键id是非自增的,这个时候就会导致页分裂,也会导致B+树节点分裂。

什么是页分裂:

首先来一张数据页的图

上面就是数据也的结构了,两个数据页之间会有指针指向上一个和下一个数据页,形成一个双向链表,(也就是InnoDB中B+Tree的叶子节点)在数据页中存储的就是一行行数据了,每个数据行之间会有单向指针连接,组成一个单向链表,假设你不停的往表里插数据,那么刚开始就会在一个数据页里面插入数据,比如说我们在左侧的数据页中插入数据,先插入主键id为1,3,5的数据,数据越来越多,我们就要搞另外一个数据页,这个数据页里面我们就插入了主键id为2,4,6的数据,关键点来了,当我们使用索引的时候,最基本的条件就是后面数据页中的数据行主键值要都大于前一个数据页中数据行的主键值,所以,当我们发现后一个主键id要小于前一页的主键id值,我们就要进行数据挪动,从而满足索引的基本要求,这个过程就是页分裂如下图所示:

1)为了索引更快找到数据所以进行页分裂有以下几个作用:

读操作:对索引来说,其实就是通过平衡二叉树不断减少要筛选的数据,而主键值就是筛选的标准,以尽快定位到我们需要的数据。

写操作:在平衡二叉树中,假设插入的数据的主键是自增长的,那么根据二叉树算法会很快的把该数据添加到某个节点下,而其他节点不用动;但是如果插入的是不规则的数据,那么每次插入都会改变二叉树之前的数据状态。从而导致了页分裂。直白一点来讲就是为了更快的找到需要的数据。

那么从B+树的角度来看也可以看出,当插入非自增的数据时,B+树也会进行分裂,详情如下个动图所示:

我们用col3这个列建索引,注意:InnoDB表只有一个聚集索引

为了节省存储空间,为了保证数据的一致性,减少他的复杂度,减少了出现行移动或者数据页分裂时二级索引的维护工作(当数据需要更新的时候,二级索引不需要修改,只需要修改聚簇索引,一个表只能有一个聚簇索引,其他的都是二级索引,这样只需要修改聚簇索引就可以了,不需要重新构建二级索引)否则,你就需要在每个索引文件中进行数据更新。

五、联合索引的底层存储结构

然后我们建立联合索引

索引是帮助MySQL高效获取数据的排好序的数据结构,联合索引想当然的就是已经排好序的B+树结构,我们这里使用了一个三个字段的联合索引,那么他是如何存储的呢?带着这个问题我们一起取了解一下联合索引的存储结构。

通常我们在建立联合索引的时候,也就是多个字段建立索引,mysql都会让我们选择索引的顺序,比如我们想在a,b,c三个字段建立一个联合索引,我们可以选择自己想要的优先级,a、b、c,或者是b、a、c 或者是c、a、b等顺序。为什么数据库会让我们选择字段的顺序呢?不都是三个字段的联合索引么?这里就引出了数据库索引的最左前缀原理。

mysql建立多列索引(联合索引)有最左前缀的原则,即最左优先,如:

如果有一个2列的索引(col1,col2),则已经对(col1)、(col1,col2)上建立了索引,当然在红黑树中,也是排好序来维护此索引;

如果有一个3列索引(col1,col2,col3),则已经对(col1)、(col1,col2)、(col1,col2,col3)上建立了索引;

这个sql语句是不会走index1索引的,

这个语句也不会走index1索引。

比如:索引index1:(a,b,c)有三个字段,我们在使用sql语句来查询的时候,会发现很多情况下不按照我们想象的来走索引。

什么语句会走index1索引呢?

答案是:

我们可以发现一个共同点,就是所有走索引index1的sql语句的查询条件里面都带有a字段,那么问题来了,index1的索引的最左边的列字段是a,是不是查询条件中包含a就会走索引呢?

这个sql语句呢?

这也是最左前缀原理的一部分,索引index1:(a,b,c),只会走a、a,b、a,b,c 三种类型的查询,其实这里说的有一点问题,a,c也走,但是只走a字段索引,不会走c字段。我们可以发现一个共同点,就是所有走索引index1的sql语句的查询条件里面都带有a字段,那么问题来了,index1的索引的最左边的列字段是a,是不是查询条件中包含a就会走索引呢?

那么这是为什么呢?

如上图所示,我们给(name,age,id)三列建联合索引,你们可以发现,在name相等的情况下(篮框部分),age字段(红框部分)的数据是有序的,但是放在整张表中看去,age字段(红框部分)的数据是乱序,何为索引?索引是帮助MySQL高效获取数据的排好序的数据结构,而age字段放在整张表中已经是乱序,已经不符合索引的原理,例如我想找到age为12的数据,如果是排好序的,我就在找到他以后就不需要再继续往下找了,因为后面的都是比我大的,但是在乱序的情况下,我就需要全表扫描进行寻找,有索引和无索引效果一样的,所以

只有上述三句会走联合索引,三个字段以此类推…

通过非主键索引查询数据时,会先查找到主键索引,然后再到主键索引上去查找对应的数据,这个过程叫做回表

联合索引:指索引中包含多个列

覆盖索引:指的是从索引中可以得到所有想要查询的列

联合索引是说的是where后面的部分,即查询条件;覆盖索引是说的select后面的部分,即查询列。
假设我们有id(主键),age,adderss,name这四个字段,且age,name两列为联合索引的两个列

这是覆盖索引,因为不会回表查询。

因为联合索引的叶子节点就包括联合索引中包含的列(age,name)还有主键(id),所以不需要回表。

这不是覆盖索引,因为会回表查询。address并不存在于联合索引的叶子节点之中,所以需要根据主键值进行回表查询

Original: https://www.cnblogs.com/sakela/p/16668635.html
Author: 萨科拉
Title: 一文搞懂mysql索引底层逻辑,干货满满!

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/622713/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • RabbitMQ实现订单超时案例

    人间清醒 用戶在购买商品的时候通常会预购然后没付款,没付款的订单通常会被设置一个自动超时时间如30分钟后超时,所以我们要在订单到30分钟后自动将超时的订单取消。 DelayQueu…

    Java 2023年6月5日
    071
  • 英语自我介绍

    问候语 个人情况介绍:姓名,年龄,故乡,本科院校和专业,注意本科院校的翻译不要错 本科经历:包括学习成绩,学生工作经历,社会活动,竞赛情况,实习经历,论文阅读情况(没有的话就从寒假…

    Java 2023年6月7日
    042
  • 使用 Sigil 制作一本关于写出好代码的epub电子书

    引子 之前写了一些关于如何写出好代码的文章,在随笔分类”代码修行”下。打算制作一本电子书,将其中的重要经验总结起来,也是对自己十年编程生涯的一个交代。 了解…

    Java 2023年6月9日
    0102
  • AnotherRedisDesktopManager下载安装与连接Redis数据库

    一、AnotherRedisDesktopManager下载 作者在gitee与github上皆提供了下载 gitee下载地址:AnotherRedisDesktopManager…

    Java 2023年6月8日
    075
  • 源码揭秘mybatis日志实现的原理

    背景 在程序开发过程中,为了调试方便、了解程序的运行过程,进行必要的日志输出总是免不了的。对于使用Mybatis而言,我们常见的需求是希望可以在日志中打印出Mybatis执行过程中…

    Java 2023年5月30日
    070
  • 最简单的单线程变多线程的例子

    最简单的单线程变多线程的例子 背景 不知道你项目里有么有这样一个函数,这个函数里调用了大概十几来个函数,这十几个函数依次的从头写到位,而且这几个函数都是相对独立的,谁先执行谁后执行…

    Java 2023年6月8日
    078
  • ArrayList源码(一)

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月9日
    069
  • 好书推荐之《深入理解计算机系统》

    大佬推荐 首先推荐的是翻译版图书《深入理解计算机系统》,原书名为《Computer Systems A Programmer’s Perspective》。不过,这本书…

    Java 2023年6月15日
    079
  • 家教小程序的设计与实现

    摘要 随着社会和技术的飞快发展,网络逐渐成为人们生活中不可或缺的存在,不管是生活、工作还是学习网络都可以给我们带来便捷。家教程序为学生和老师提供更加快捷的平台,相对舒适的工作环境,…

    Java 2023年6月7日
    072
  • 2020年10月23日笔记

    Java8特性待更新 在公司项目里面有很多这类代码,熟练使用后能够加快开发速度。1、快速便利map的方法map进行快速遍历的方法map.forEach((key,value)-&g…

    Java 2023年6月13日
    056
  • AOP介绍

    java;gutter:true;/**作者:呆萌老师*☑csdn认证讲师*☑51cto高级讲师*☑腾讯课堂认证讲师*☑网易云课堂认证讲师*☑华为开发者学堂认证讲师*☑爱奇艺千人名…

    Java 2023年6月13日
    061
  • LeetCode随缘刷题之转化成小写字母

    这道题应该是最简单的一道题了把,简直在侮辱我。 package leetcode.day_12_12; public class ToLowerCase0709 { public …

    Java 2023年6月7日
    057
  • ArrayList扩容代码分析

    ArrayList扩容机制是在面试中频繁出现的问题,平时了解的比较含糊,特此记录! 注意:每次发生扩容,其容量扩充为原来的1.5倍 左右 add方法 public boolean …

    Java 2023年6月15日
    077
  • 软件需求分析与系统设计笔记

    简介 软件过程模型 瀑布模型 快速原型模型 增量模型 螺旋模型 喷泉模型 各模型优缺点: 简介 什么是软件? 软件是计算机系统中与硬件(hardware)相互依存的另一 部分,是程…

    Java 2023年6月15日
    084
  • 【算法】冒泡排序算法

    冒泡排序算法 简介:冒泡排序是对数组进行排序的一种方式,常见的排序算法还有选择排序,插入排序、希尔排序、归并排序、堆排序等,这里介绍最简单的排序算法——冒泡排序。 思路 假设长度为…

    Java 2023年6月9日
    0106
  • SSM-注解开发

    Day4 Spring 注解开发 Srping原始注解主要是替代Bean标签配置 Spring 原始注解 @Component 在类上实例化Bean @Controller 在we…

    Java 2023年6月5日
    058
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球