MySQL中深入浅出索引

news2025/1/15 16:57:07

文章目录

  • 前言
  • 一、索引的常见模型
  • 二、InnoDB的索引模型
  • 三、索引的维护
  • 四、索引的优化
      • 覆盖索引
      • 联合索引
      • 最左前缀原则
      • 索引下推


前言

我们在看书的时候,打算回看某一个桥段的内容时。这是你肯定会是先翻看书的目录,从目录确定这段内容的位置,然后再到确切的页中去寻找。你肯定不会,从书的第一页一页一页的翻找。同样,数据库也是,查找数据也不会从一条记录一条记录的寻找。也肯定是先从像书的目录一样的中确定了,才寻找。而这个像书的目录一样的,在数据库中我们称为索引。我们这篇文章就是对MySQL中的索引进行的总结学习


一、索引的常见模型

索引就像我前言中介绍的和书的目录一样,主要是为了提高查询效率的。通过我们对数据结构的了解,可以提高读写效率的数据结构很多。
这以我们主要介绍三种常见的数据结构,分别是哈希表、有序数组和搜索树。
接下来我们从使用性能上,对这三种数据结构进行一个分析。

哈希表:
众所周知,哈希表是一种以键-值(key-value)存储数据的结构。这里对哈希表的基本概念不再赘述,不了解的可以百度搜索以下。这里主要举个用哈希表的例子:
假设,现在维护着一个身份证信息和姓名的表,需要根据身份证号查找对应的名字,这时对应的哈希索引的示意图如下所示:
在这里插入图片描述
从图中可以看出,User2和User4根据身份证哈希算出来的值都是N,后面紧跟着一个链表进行存储。这时如果要查ID_card_n2对应的名字是什么,处理步骤就是:首先,将ID_card_n2通过哈希函数算出N,然后按顺序遍历,找到User2。注意,链表中存储的ID_card_n的值并不是递增的,这样虽然增加的时候比较方便,直接追加即可。但是查询的时候效率较低,需要从头遍历。
像上述,一个一个查询数据的时候用哈希作为索引结构问题不大。但如果是范围查询,比如查找身份证在[ID_card_X,ID_card_Y]区间的所有用户,那么就必须全部扫描一遍,这样显的效率就比较低了。
所以,哈希表这种结构适用于只有等值查询的场景

有序数组:
有序数组在等值查询和范围查询场景中的性能就都非常优秀。还是以上面的身份证和名字的表为例,用有序数组存储的话,就是如下图所示:
在这里插入图片描述
这是如果要查ID_card_n2对应的名字,那么使用二分法就可以快速得到,这个时间复杂度是O(log(N))。
如果要进行范围查询,如查身份证在[ID_card_X,ID_card_Y]区间的User,可以先用二分法找到ID_card_X(如果不存在ID_card_X,就找到大于ID_card_X的第一个User),然后向右遍历,直到查到第一个大于ID_card_Y的身份证,退出循环。这么来看,用有序数组当索引,效率挺高啊。
但是,更新数据,往记录中插入数据的时候,就必须挪到后面的数据,成本太高。
所以,有序数组索引只适用于静态存储引擎。只适合存储那些不会再修改的数据。

二叉搜索树:
我们依旧使用上面身份证和名字的例子,如果用二叉搜索树来存储,如下图所示:
在这里插入图片描述
二叉搜索树的时间复杂度是O(log(N))。为了维持O(log(N))的查询复杂度,就需要保持这棵树是平衡二叉树,这是理论情况下。但是实际存储中,索引不仅存在内存中,还要写到磁盘上。而此时如果使用的是平衡二叉树,那么数据量大的时候,那么层高会很高,那么访问磁盘操作就会很耗时。
因此,为了查询尽量少读磁盘,就必须让查询过程访问尽量少的数据块,此时不应该使用二叉树,而应该使用N叉树来降低树高。

到此,我们已经了解了三个基本数据存储模型。

接下来,我们来深入了解下,MySQL中的InnoDB的索引模型

二、InnoDB的索引模型

在InnoDB中,表都是根据主键顺序以索引的形式存放的,并且InnoDB使用的是B+树索引模型,数据都是存储在B+树中的。每一个索引在InnoDB里面都对应一棵B+树。

接下来我们以一个例子进行以下讲解:
假设,我们创建了一个主键为ID的表,表中有字段k,并且在k上有索引。建表SQL语句如下:

create table T(
	id int primary key,
	k int not null,
	name varchar(16),
	index(k) )engine=InnoDB;

表中R1~R5的(ID,k)值分别为(100,1)、(200,2)、(300,3)、(500,5)和(600,6)。这两棵树的示例图如下所示:
在这里插入图片描述
从图中可以看出,根据叶子节点的内容,索引类型可以分为主键索引和非主键索引。

其中,主键索引的叶子节点存的是整行数据,在InnoDB里,主键索引也被称为聚簇索引。
非主键索引的叶子节点内容是主键的值,在InnoDB里,非主键索引也被称为二级索引。

由此,我们可以看出,主键索引和普通索引存储的内容是不同的。那么进行查询的时候,肯定会有区别的。

接下来,我们接着以上面创建的那个T表。执行如下两个SQL查询语句看看区别:

  • select * from T where ID=500;主键查询方式,此时进行搜索的时候只需要搜索ID这棵B+树就好。
  • select * from T where k=5;普通索引查询方式,此时进行搜索的时候需要先搜索k索引树,得到ID的值为500,再到ID索引树搜索一次。这个过程称为回表

不知你是否还记得,我们在将有序数组这个基础结构进行记录存储的时候,说到对于更新数据添加记录,都会涉及到记录的整体后移,整体效率比较低。

那么对于这B+树呢?毕竟它也是有序的。那么对于数据记录的更新,B+树我们是怎么维护的呢?
接下来我们来学习一下

三、索引的维护

我们接着以上面创建的那个T表为例。

如果此时,我们插入新的行ID值为700,则只需在R5的记录后面插入一个新的记录。

但是如果我们新插入的ID值为400,那么就需要逻辑上挪动后面的数据,空出位置。
并且如果此时,也比较倒霉,R5所在的数据页已经满了。这是还需要根据B+树的算法,申请一个新的数据页,然后挪动部分数据过去,这个过程称为页分裂。这种操作必定对性能有影响。除了性能之外,页分裂还会影响数据页的利用率。毕竟原本放在一个页的数据,现在分到两个页中,整体空间利用率降低大约50%。

知道了添加数据会存在页分裂。那么删除数据的时候,就有可能会引起页合并。当两个相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。

从上面我们也知道了,使用B+树做索引结果,可能会引起页分裂,影响性能。那么有没有方法避免这种情况呢。接下来我们分析下看;

通过页分裂的讲述,我们知道导致产生页分裂,是因为添加数据的时候,不是按照索引递增添加的,所以会涉及到数据的挪动。为了解决这个问题,在建表规范的时候,有这么一句叙述,要求建表语句里一定要有自增主键。当然事无绝对,我们来分析一下哪些场景下应该使用自增主键,而哪些场景下不应该:

还是接着上面的创建的T表。如果我们把表里的主键ID改为自增主键,那么插入新记录的时候可以不指定ID的值,那么系统会获取当前ID最大值加1作为下一条记录的ID值。符合递增插入的场景,每次插入新记录都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。

创建的这个T表只是我们能的一个demo。对于在实际开发中有业务逻辑的。如果业务逻辑字段做主键,那么插入数据的时候,不一定是按序插入的,就可能会涉及到分页。并且针对存储空间来看,如果表中只有一个唯一字段,如字符串类型的身份证号,那么应该用身份证号做主键,还是用自增字段做主键呢?

因为每个个非主键索引的叶子节点上都是主键的值。如果用身份证号做主键,那么每个二级索引的
叶子节点占用约20个字节,而如果用整型做主键,则只要4个字节,如果是长整型(bigint)则是8个字节。由此可见,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。 所以,从性能和存储空间方面考量,自增主键往往是更合理的选择。

我们清楚了用自增主键的业务场景,那么有没有什么场景适合用业务字段直接做主键呢?

那当然有,比如,有些业务的场景需求是这样的:

  1. 只有一个索引
  2. 该索引必须是唯一索引

这就是典型的KV场景。由于没有其他索引,也就不用考虑其他索引的叶子节点大小的问题。

了解了这些问题之后,接下来我们对索引进行深入的学习。即实际使用中有关索引的一些优化

四、索引的优化

首先在讲解之前,我们也先创建个表T,通过实际查询语句来进行分析。
创建T表的语句如下:

create table T(
	ID int primary key,
	k int NOT NULL DEFAULT 0,
	s varchar(16) NOT NULL DEFAULT '',
	index k(k) )engine=InnoDB;

往表里插入数据的语句如下:

insert into T values(100,1,'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');

ID和k索引列创建的B+树,如下:
在这里插入图片描述
此时,我们如果执行如下查询语句:

select * from T where k between 3 and 5;

这条SQL查询语句执行流程如下:

  1. 在k索引树上找到k=3的记录,取得ID=300;
  2. 再到ID索引树查到ID=300对应的R3;
  3. 在k索引树取下一个值k=5,取得ID=500;
  4. 再回到ID索引树查到ID=500对应的R4;
  5. 在k索引树取下一个值k=6,不满足条件,循环结束。

通过上述执行过程,我们可以看到,这个查询过程读了k索引树的3条记录(步骤1、3和5),回表了两次(步骤2和4)。

众所众知,回表操作是非常耗时的一件事,降低索引的性能。那么有没有可能经过索引优化,避免回表过程呢?

覆盖索引

如果要执行的查询语句是:

select ID from T where k between 3 and 5;

这时只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。在这个查询里面,索引k已经“覆盖了”我们的查询需求,我们称为覆盖索引
因为覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

明白了用覆盖索引的优化。紧接着我们再引出一个优化手段

联合索引

假设我们有一张市民的表,创建语句如下:

create table tuser(
	id int(11) NOT NULL,
	id_card varchar(32) DEFAULT NULL,
	name varchar(32) DEFAULT NULL,
	age int(11) DEFAULT NULL,
	ismale tinyint(1) DEFAULT NULL,
	PRIMARY KEY(id),
	KEY id_card(id_card),
	KEY name_age(name,age)
	)ENGINE=InnoDB;

这里我们来讨论个问题,有没有必要将身份证号和名字建立联合索引?

此时我们会想,如果有根据身份证号查询市民信息的需求,我们只要在身份证号字段上建立索引就够了。而再建立一个(身份证号、姓名)的联合索引,是不是浪费空间?

但是,如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,这个联合索引就有意义了。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间

但是,需要另外建立索引。那么对于这 建立的冗余索引,浪费的存储空间,以及索引字段维护的这些代价都需要我们进行权衡考虑的。

说到了联合索引,那就无法避免的聊到匹配的原则了。

最左前缀原则

上面讲的对于高频请求,根据市民身份证查询姓名,可以建立个联合索引。那么如果也有根据身份证查询地址是不是也需要单独建立个联合索引呢?

这里,便引出了我们这个部分的学习。其实,在B 树这种索引结构,可以利用索引的 树 “最左前缀”,来定位记录。

为了清楚的说明,我们还是接着用一个实例进行解释。我们用(name,age)这个联合索引来分析。索引示意图如下图所示:
在这里插入图片描述
从图中,我们可以看到,索引项是按照索引定义里面出现的字段顺序排序的。

从这个数据表中,如果要查找所有名字是“张三”的人时,可以快速定位到ID4,然后向后遍历得到所有需要的结果。
而,如果这是我们要查找第一个字是“张”的人,即SQL语句的条件如下:

where name like '张%;

这时,也能够用上这个索引,查找到第一个符合条件的记录是ID3,然后向后遍历,直到不满足条件为止。

由此可见,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左
前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。

知道了索引的最左匹配原则,那么我们在建立联合索引的时候,对索引内的字段顺序排列就十分重要了。

基于索引的复用能力。因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建立索引了。因此,第一原则是,如果通过调整顺序,可第以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的

这是回到开头,用身份证查询地址的需求。因为根据前面,我们知道也需要建立(身份证,姓名)这个联合索引,并且身份证在最前头,那么我们再在这联合索引后面加上地址这个列,就也可以满足我们的需求了啊,并且也不需要另外创建索引了。

但是,需要注意的是联合索引也不是万能的。如果对于表的查询语句,既有联合查询,也有对于单个列的查询。比如还是对(a,b)这个联合索引来说,如果查询条件里面只有b的语句,是无法使
用(a,b)这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护(a,b)、(b) 这两个索引。这是我们就需要考虑空间了。

学完这之后,我们在接着对所有的另一个优化。

索引下推

我们依旧是用例子进行讲解。
我们还是使用上面的市民表的联合索引(name,age)为例。如果有一个需求,检索出表中“名字第一个字是张,而且年龄是10岁的所有男孩”。SQL语句如下:

select * from tuser where name like '张%' and age=10;

根据我们讲述的索引匹配原则。肯定会先到联合索引的搜索树上,找到第一个满足名字的第一个字是“张”的ID3记录。那然后呢?

对于在MySQL5.6之前,此时只能从ID3开始一个个回表,到主键索引找出数据行,再对比字段值。流程如下图所示 :
在这里插入图片描述

而在而MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。流程如下图所示:
在这里插入图片描述
可以看到,引入了索引下推的,这样对查询条件中的联合索引的其他字段也先检索一下。这样大大减少了回表操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/162556.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

爬虫利用多线程快速爬取数据

一般单线程爬数据太慢了话,就采用多线程。 一般要根据两种情形来选择 自定义线程线程池 往往最关键的地方在,多个线程并发执行后,是否需要线性的返回结果。也就是先调用的线程,返回的结果要在前面。 或者说,某个对…

mysql简单数据查询——数采数据电量与耗料的日统计

目录 前言 步骤1:date_format函数 步骤2:concat函数 步骤3、4:查询中使用变量 完整代码 前言 在数采数据已写入mysql数据库中后,进行数据处理,统计电量与耗料数据 由于数据库版本较低,无法使用较新的…

华为策略路由实验配置

配置接口相关的IP地址,并配置IGP路由协议使得全网互通 AR1配置接口策略路由 对经过本地转发的路由生效,对本地始发的路由不生效 配置nqa检测下一跳状态 nqa test-instance PC1 icmptrace nqa的管理者为PC1,NQA的测试例名为icmptrace test-…

全国青少年软件编程(Scratch)等级考试二级考试真题2022年12月——持续更新.....

1.一个骰子,从3个不同角度看过去的点数如图所示,请问5的对面是什么点数?( ) A.1 B.3 C.4 D.6 正确答案:A 答案解析: 根据图三,用右手定则,大拇指朝上指向6所对的方向,其余四指握起来表示旋转方向,可以看到先5后2,然后把这个姿势对应到图1中,就知道1的对面是5…

C语言进阶(8)——动态内存空间管理

前言 文章目录前言1.为什么存在动态内存分配2.动态内存函数的介绍2.1 malloc函数2.2 free函数2.3 calloc2.4realloc3 常见的动态内存错误4.经典笔试题题目 1:题目 2:题目 3:题目 4:5.C/C程序的内存开辟6.柔性数组6.1 定义6.2 柔性…

1.5、中断和异常

整体框架 1、中断的概念和作用 当中断发生时,CPU 立即进入核心态\color{red}核心态核心态 当中断发生后,当前运行的进程暂停运行,并由操作系统内核对中断进行处理 对于不同的中断信号,会进行不同的处理 发生了中断&#xff0c…

记录--“非主流” 的纯前端性能优化

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 性能优化一直是前端研究的主要课题之一,因为不仅直接影响用户体验,对于商业性公司,网页性能的优劣更关乎流量变现效率的高低。例如 DoubleClick by Google 发现&…

MD5加密

MD5加密 md5加密 明文 加密变成 128位二进制 --> 32位16进制字符串的密文 MD5特征: 明文一样, 得到密文一样密文一样, 推出明文一样明文不一样, 得到密文不一样 缺点: 现在网上有很多暴力破解的网址,直接使用md5加密还是不太安全 为了提高安全性&am…

Linux常用命令——vmstat命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) vmstat 显示虚拟内存状态 补充说明 vmstat命令的含义为显示虚拟内存状态(“Viryual Memor Statics”),但是它可以报告关于进程、内存、I/O等系统整体运行状态。 语法 vmst…

Java高并发编程实战,异步注解@Async自定义线程池

一、Async注解 Async的作用就是异步处理任务。 在方法上添加Async,表示此方法是异步方法; 在类上添加Async,表示类中的所有方法都是异步方法; 使用此注解的类,必须是Spring管理的类; 需要在启动类或配置类…

ELK日志(1)

Elasticsearch开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful 风格接口,多数据源,自动搜索负载等。RESTFUL特点包括&#xf…

MES系统之工控

MES系统之工控 要控制MES系统首先要对他有个了解。MES系统最早由1990年,由美国先进制造研究中心AMR提出的,当时中文意思叫制造执行系统概念。直到1997年,MESA(制造执行系统协会)提出了MES功能组件和继承模型,到20世纪90年代初期&a…

动态内存管”家“

🐋动态内存管理🦖动态内存分配存在的意义🦖动态内存函数的介绍🐤malloc和free🐤calloc🐤realloc🦖常见动态内存错误🐤对空指针的解引用操作🐤对动态开辟空间的越界访问&a…

springMVC的响应

SpringMVC接收到请求和数据后,进行一些了的处理,当然这个处理可以是转发给Service,Service层再调用Dao层完成的,不管怎样,处理完以后,都需要将结果告知给用户。 对于响应,主要就包含两部分内容&…

关于 sensor hdr 模式下不出图/出图异常的排查方法

1、问题背景:有项目调试过 ov02k10(1920*1080)和 sc301IoT(2048*1536)两款 sensor, 都有出现 hdr 模式下出图异常或者不出图的问题,总结下排查过程及注意事项;2、问题现象:a、ov02k1…

Odoo 16 企业版手册 - 库存管理之寄售

寄售 使用“「设置」”菜单下提供的「寄售」选项,可以对库存中储存的产品设置所有者。产品将由零售商销售,但产品的实际所有权将由供应商持有,直到产品出售给客户。通过这种方法,您可以轻松地将未售出的产品退还给供应商。在寄售的…

java对接阿里云短信服务详解(验证码,推广短信,通知短信)

前言 小前提: - java:springboot框架,maven版本管理。 - 阿里云:有账号,已经进行实名认证。 java对接阿里云短信服务详解(验证码,推广短信,通知短信)前言1. 登录阿里云进…

基于servlet+mysql+jsp实现体育用品商城

基于servletmysqljsp实现体育用品商城一、系统介绍1、系统主要功能:2、环境配置二、功能展示1.主页(客户)2.登陆(管理员)3.主页(管理员)4.订单管理(管理员)5.客户管理(管理员&#x…

linux系统结构

目录 0.前言 1.系统结构图 1.1.操作系统工作方式 1.2.高版本和低版本内核区别 0.前言 本专栏,是记录内核学习的,参考b站linux内核源码分析,以及linux内核艺术图解。后面的文章将记录个人的学习,源码注释,源码理解…

ANSYS Products 2020 R1 Linux64版本安装

fluent系列 占位 fluent2020R1版本安装fluent系列前言一、基础环境二、安装准备1.图形化环境准备2.路径准备3.挂载安装用iso4.拷贝安装文件三、开始安装1.进入图形化界面2.开始安装3.试运行fluent四、替换破解版的license总结前言 在centos7环境下安装使用fluent的部署记录。…