MySQL优化(2):索引与优化原理(上)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

上一篇,我们重走了一遍数据库索引的历史,认识了B+树结构,这一篇我们回归现实中的MySQL数据库,学习具体的SQL优化原则,并从索引底层原理出发,分析为什么会有这些优化原则。

提到索引,很多人就会说:哦,索引能提高查询速度。一般这么说的人,可能学得还不错,但绝对还没有完全掌握索引的底层原理。

索引的类型

打开Navicat,尝试创建索引时会发现有4种索引类型可以选择:

  • 全文索引
  • 普通索引
  • 空间索引
  • 唯一索引

普通索引就可以组织树结构了,而唯一索引在普通索引的基础上还要求索引列不能重复。比如,假设我们给student表的name列加了唯一索引,如果表中已经存在"张三",那么再次插入"张三"将会报错。

MySQL这种关系型数据库并不适合进行全文检索(考虑Elastic Search),所以全文索引一般很少使用。

至于空间索引,我也不知道是什么。

实际开发常用的索引只有普通索引和唯一索引,其他的可以不用理会。

索引的实现方法

索引的实现方式一般有两种,通过B+树结构或hash算法实现。

特别注意,这里虽然写的是"BTREE",但MySQL确实使用的是B+Tree。

这个概念,其实和上面“索引的类型”并不冲突。

比如,对于普通索引,我们可以使用B+树的结构组织索引,也可以使用hash算法实现。经过上一篇的学习,我们对B+树结构已经比较了解,所以这里单独聊一下hash索引。

所谓hash索引,其实就是利用哈希算法为索引列计算得到唯一的存储地址,一般来说这个地址是不会重复的(重复的情况被称为哈希冲突)。

举个燕十八老师说的例子:

在墙上装一根弹性永不衰变的弹簧,每次拿不同的物件把弹簧压到极限后放开,不同的物件最终落点会不同。比如你上回存了一本书,那么下次想要找到这本书时,只需要拿一本 一模一样的书重新弹一下,即可在本次落点处找到上次那本书。

对应到实际的数据库索引设计上,就好比你存入id=10086的数据时,根据id弹一下得到了一个落点并存储数据,下次拿着id=10086查询时,只要把id再弹一下,就能马上找到对应的行数据,是不是很快呢~

而所谓的哈希冲突,指的是某次你换成一个书包测试时,结果因为空气阻力或重量等综合因素作用,落点竟然和某本书是一样的,也就是落点发生了冲突。

但上面的比喻其实不准确,会让人误以为越重的物品落点越近,越轻的物品落点越远,误以为hash索引可以进行范围查找。我们先来了解下hash索引的优劣,再来解释为什么hash索引不适合做范围查询。

hash索引的优劣势

  • 优势:速度非常快,只需一次计算即可得到地址,时间复杂度O(1),而B+树是O(logn)
  • 劣势:对于范围查找无能为力,只能逐次计算得到所有数据,而B+树叶子节点是有序链表,范围查询非常方便。另外,哈希算法本身代表着精确定位,依赖于计算的入参得出唯一的值,所以无法进行模糊匹配。你给我"bravo",我可以计算唯一的hash值,你给我"bra%",我会以为这人就叫"bra%",也计算一个值,但这个值代表着"bra%"计算得到的落点,而不是"所有以bra开头的数据"的落点,显然是不对的

hash索引为什么不适合做范围查询呢?通常做范围查询时,我们只需要计算两个边界值,处于边界值之间的数据都是符合要求的。但对于hash索引,这是不成立的,因为hash本身就是散列的,它的落点其实是没有规律的。

比如你想要重量在500克~1000克的全部书籍,但这个区间内的数据不一定就是500~1000克的书(对于真正的hash算法,重量与距离并不是正相关,所以我说上面的弹簧例子不是很恰当)。比如在JavaSE阶段接触HashMap时,大家也发现了,put的顺序和遍历的顺序并不一定相同。比如第一次put存了1001克的书,第二次put存了500克的书,你以为取出的顺序是1001克、500克,其实是500克,1001克(顺序)。

说到hash索引,还有人记得《实用小算法》中List转HashMap的操作吗?其实就是借鉴hash索引!

索引的创建

索引的创建时机有两处:

  • 起初,建表时顺便建立索引
  • 后期,修改表结构创建索引

比如,一开始就创建索引:

这张表有两个索引:主键索引、auditor_id普通索引。

主键索引并不属于上面介绍的4种索引类型之一,但所谓的Primary Key可以看做 唯一索引 + NOT NULL约束。

后期如果需要添加索引,可以通过两种方式:SQL语句、Navicat图形界面。

-- 1.添加PRIMARY KEY(主键索引)  
ALTER TABLE `table_name` ADD PRIMARY KEY (`column`) ;
 -- 2.添加UNIQUE(唯一索引)  
ALTER TABLE `table_name` ADD UNIQUE (`column`); 
-- 3.添加INDEX(普通索引)  
ALTER TABLE `table_name` ADD INDEX index_name (`column`); 
-- 4.添加FULLTEXT(全文索引)  
ALTER TABLE `table_name` ADD FULLTEXT (`column`); 
-- 5.添加联合索引  
ALTER TABLE `table_name` ADD INDEX index_name (`column1`, `column2`, `column3`);

在本案例中,可以写:

ALTER TABLE `moneywithdraw` ADD INDEX idx_auditor_id (`auditor_id`);

利用Navicat图形界面创建单列索引:

利用Navicat图形界面创建联合索引:

索引的好与坏

文章开头有一句话:

提到索引,很多人就会说:哦,索引能提高查询速度。一般这么说的人,可能学得还不错,但绝对还没有完全掌握索引的底层原理。

如果你认为索引的优势只是加快查询,那就太小看索引了。

索引的优势是:

  • 加快查询速度(包括关联查询)
  • 加快排序速度(ORDER BY)
  • 加快分组速度(GROUP BY)

虽然加快排序、加快分组最终还是体现在加快查询速度上,但能主动意识到这一点算是一种突破,只有你意识到索引能加快排序和分组,你才会在写ORDER BY和GROUP BY时有意识地利用索引(最左匹配原则),从而写出更优的SQL。

索引的劣势:

  • 对索引列的增删改需要额外维护索引,也就是说索引能提高查询速度,但往往会降低增删改的速度
  • 日常开发通常是建联合索引,而联合索引需要考虑索引失效问题
  • 太多的索引会增加查询优化器的选择时间

建索引的原则

很多人觉得SQL优化才是重中之重,创建索引只需要一行代码即可,没什么大不了的。但现在你已经知道了索引的优势与劣势,你会明白“在合适的字段建立索引”是多么空泛的口号。创建索引的判断依据究竟是什么呢?

创建索引有4个大原则:

  • 联合索引应该优于多个单列索引
  • 索引应该建立在区分度高的字段上
  • 尽量给查询频繁的字段创建索引,避免为修改频繁的字段创建索引
  • 避免重复索引

第一个原则背后的原因是,实际上数据库一次查询只会选择一个索引(不包括回表),更专业的说法是每次查询只会选择一个执行计划。所以即使你给所有列都加了索引,SELECT xx, xxx FROM table WHERE ...时,数据库也只会择优选择一个执行计划进行查询。既然其他索引帮不上忙,又会增加维护负担,为何我们还要执意创建多个单列索引呢?

看到这,你可能会反问:我靠,那MySQL也太笨了吧,为什么这么死心眼一次只利用一个索引?

我个人的理解是,索引本身的出发点是“走完一遍索引后,数据库应该返回精确的结果或很小的结果集”,此时再走一遍索引还不如直接遍历结果集来得快。当然,这个假设是建立在“你建立的索引区分度很高”的基础上。

什么是区分度很高?这就是第二个原则。比如,表中有100w学生数据,你如果在sex列加索引,那么根据sex大概只能筛选出50w,这是一个很大的结果集,此时这个索引就很差,因为区分度太低了。

第三个原则就是字面意思,总之要意识到索引在加快查询的同时几乎必然会对修改产生负担,所以创建索引并没有那么简单,它绝对是一门“平衡的艺术”。

第四个原则是,比如已经建立a索引,又建立index(a,b,c)联合索引,此时单列索引a就是冗余的,因为联合索引已经可以保证符合条件时会利用a索引。在物理存储上,a单列索引和index(a, b, c)是两个独立的B+树。此时如果留着a单列索引,会增加维护成本。

以上四个原则,后面的内容还会重新提到。

MySQL常用引擎

MySQL的引擎有很多种,但最常听到的就MyISAM和InnoDB,而实际开发几乎99%选择使用InnoDB,而且MySQL5.6还是哪个版本以后默认引擎就从MyISAM变成了InnoDB,所以这里着重介绍InnoDB,简略介绍MyISAM。

对于两种引擎的介绍,可以看下面

这里主要想和大家讨论MyISAM和InnoDB在索引组织上的区别。大家应该都已经知道,MyISAM和InnoDB存储数据的方式是不同的。

MyISAM的每张表在存储时会分为3个文件:

  • 表结构
  • 表数据
  • 索引

也就是说,表数据和索引是分别独立存储的。

而InnoDB的表数据在存储时只分为2个文件:

  • 表结构
  • 表数据+索引

需要注意的是,InnoDB所有表的数据和索引都在同一个文件里(见下一个小节)。

聚簇索引与非聚簇索引

对于BTREE索引而言,从数据的组织形式来看,索引又可以分为两大类:

  • 聚簇索引
  • 非聚簇索引

所谓聚簇索引,可以简单理解为索引和数据是“聚合”在一起的,而非聚簇索引的数据和索引是分开的。

对于MyISAM和InnoDB,可以简单总结如下:

  • 非聚簇索引(MyISAM)
  • 聚簇索引(InnoDB)
    • 主键索引:叶子节点是表数据
    • 辅助索引(唯一索引、普通索引):叶子节点是主键,必要时需要根据主键回表查询

对于MyISAM而言,根据索引快速查询到目标结果后,往往都需要“回表”。所谓“回表”,一般指的是根据索引确定数据行以后,由于缺少其他列的字段,需要回到数据表中取得其余数据。在这句话描述中,索引和数据表是独立的,这也确实是MyISAM的文件结构。

而InnoDB引擎的主键索引无需回表,每一行完整的数据都直接挂在叶子节点下,可以直接返回。对于InnoDB的主键索引而言,数据即索引,索引即数据。

InnoDB的主键索引与辅助索引

InnoDB的索引属于聚簇索引,但并不是说InnoDB下的所有索引下面都会挂着数据。假设一个场景:

用户新建表后,自然会有主键索引。但后期发现name字段查询很频繁,于是加了name索引。

如果name索引(辅助索引)也和主键索引一样挂着数据,那么两个索引数据就会重复。相当于一张student表,存了两份数据。且不说数据冗余,更新时还可能产生数据不一致。

所以InnoDB的做法是,辅助索引只存储索引列+主键,必要时需要“回表”:

由于SELECT * FROM stu WHERE name='bravo'中,查询的数据是*,也就是整行数据。而上面的辅助索引只存了主键+name,所以必须回表,拿着主键再去跑一遍主键索引,最终返回数据。

总的来说,MyISAM由于是非聚簇索引,决定了它必须回表,而InnoDB是聚簇索引,主键索引一般是不用回表的,而利用辅助索引时可能需要回表。

再聊聊回表

介绍MyISAM时,我们第一次接触“回表”的概念,因为MyISAM的索引和表数据是分开的,从索引回到表数据中查询数据的操作称为“回表”,大家很容易接受。

而在InnoDB的辅助索引上查询完毕后回到主键索引的操作也叫“回表”,这是对于InnoDB的主键索引而言,数据即索引,索引即数据,回到主键索引查询当然也算是回表查询啦。

SQL优化的本质其实就是减少/减小磁盘IO,而回表必然会增加磁盘IO次数(还是要把主键索引的一个个节点加载到内存)。

如果仔细观察MyISAM和InnoDB的回表,你会发现MyISAM的查询速度会优于InnoDB。原因在于:

MyISAM索引和数据是分离的,索引很小,必要时可以全部一次性加载进内存,磁盘IO降到最低,只需在最后执行一次回表。

而InnoDB由于是聚簇索引,索引和数据不分离,导致无法直接一次性加载索引,转而采用磁盘IO的方式分多次读取节点。最差的情况下,根据普通索引找到目标数据的id后,还需要回表查询,重走一次主键索引!

所以,一般在介绍MySQL引擎时,MyISAM的特点都是查询快,但不支持事务,而InnoDB查询稍逊,但支持事务。

InnoDB的回表与索引失效

InnoDB这种先查辅助索引,再根据辅助索引上查到的id回到主键索引查询全部行数据的行为称为“回表”。假设一种情况:一个student表,id为主键索引,name是辅助索引(普通索引或唯一索引)。对于

SELECT * FROM t_student WHERE name like 'bravo____'

假设t_student表共有10000行数据,其中name字段命名方式为"bravo1988"、"bravo1989"...,那么极有可能会发生以下情况:

MySQL沿着辅助索引(name)跑了整棵树,最终得到9999个符合条件的id,然后还要遍历9999个id,逐个再跑一次主键索引。

这种情况效率极低,所以MySQL往往选择壮士断腕,放弃辅助索引,直接全表扫描。所谓全表扫描,个人认为是走主键索引底部的链表。在上面的案例中,因为WHERE条件是name,而主键索引是id组织的,索引无法利用B+树。

对于MySQL的InnoDB索引,当利用辅助索引查询的结果集超过某个阈值(比如占总数据的80%,泛指,未求证),MySQL便会放弃走索引,直接全表扫描。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CF1899C Yarik and Array(DP,贪心)

题目链接 题目 A subarray is a continuous part of array. Yarik recently found an array a of n elements and became very interested in finding the maximum sum of a non empty subarray. However, Yarik doesn’t like consecutive integers with the same parity, s…

VS 将 localhost访问改为ip访问

项目场景: 使用vs进行本地调试时需要多人访问界面,使用ip访问报错 问题描述 vs通过ip访问报错 虚拟机或其它电脑不能正常打开 原因分析: 原因是vs访问规则默认是iis,固定默认启动地址是localhost 解决方案: 1.vs项目启动之后会出现这个 右…

【mysql】1153 - Got a packet bigger than ‘max_allowed_packet‘ bytes

执行mysql 语句出现:1153 - Got a packet bigger than max_allowed_packet bytes; 1153-得到一个大于“max_allowed_packet”字节的数据包。 数据包太大了怎么办。该配置吧。 查看max_allowed_packet show global variables like max_allowed_packet;…

js-webApi 笔记2之DOM事件

目录 1、表单事件 2、键盘事件 3、事件对象 4、排他思想 5、事件流 6、捕获和冒泡 7、阻止默认和冒泡 8、事件委托 9、事件解绑 10、窗口加载事件 11、窗口尺寸事件 12、元素尺寸和位置 13、窗口滚动事件 14、日期对象 15、节点 16、鼠标移入事件 1、表单事件 获取…

【云服务器选型指南:五大关键】

云服务器选型指南 写在前面 在云计算时代,云服务器(Elastic Compute Service, ECS)凭借其简单高效、安全可靠、处理能力可弹性伸缩等特点,成为构建稳定、安全应用的首选。相比物理服务器,云服务器的管理方式更为简单…

分类预测 | Matlab实现基于PSO-SDAE粒子群优化算法优化堆叠去噪自编码器的数据分类预测

分类预测 | Matlab实现基于PSO-SDAE粒子群优化算法优化堆叠去噪自编码器的数据分类预测 目录 分类预测 | Matlab实现基于PSO-SDAE粒子群优化算法优化堆叠去噪自编码器的数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现基于PSO-SDAE粒子群优化算法…

【Coppeliasim】 通过TCP与coppeliasim通信

仿真客户端, 代码中启动了tcp服务器。 simrequiresim socketrequiresocket-- 以下函数将数据写入套接字(仅为简单起见只处理单个数据包): writeSocketDatafunction(client,data)local headerstring.char(59,57,math.mod(#data,25…

【EI会议征稿】第七届大数据与应用统计国际学术研讨会(ISBDAS 2024)

第七届大数据与应用统计国际学术研讨会(ISBDAS 2024) 2024 7th International Symposium on Big Data and Applied Statistics 第七届大数据与应用统计国际学术研讨会(ISBDAS 2024)定于2024年3月8-10日在中国上海举行。会议旨在…

wpf devexpress Property Gird管理集合属性

Property Grid允许你添加,浏览和编辑集合属性

ESP32 MicroPython UART及小车类构造函数实验⑥

ESP32 MicroPython UART及小车类构造函数实验⑥ 1、实验目的2、实验内容3、参考代码4、实验结果 1、实验目的 控制小车动起来 2、实验内容 控制小车的前进、后退、左转、右转。读取小车 使用到的串口构造函数: uartmachine.UART(id,baudrate,rx,tx)uart:返回的构…

常见的反爬+文字加解密

一、常见的反爬介绍 基于身份识别的反爬:1.User-agent 2.Referer 3.Captcha 验证码 4.必备参数 基于爬虫行为的反爬:1.单位时间内请求数量超过一定阈值 2.相邻两次请求之间间隔小于一定阈值3.蜜罐陷阱 通过对数据加密进行反爬:1.对文字加密…

Elasticsearch:通过摄取管道加上嵌套向量对大型文档进行分块轻松地实现段落搜索

作者:VECTOR SEARCH 向量搜索是一种基于含义而不是精确或不精确的 token 匹配技术来搜索数据的强大方法。 然而,强大的向量搜索的文本嵌入模型只能按几个句子的顺序处理短文本段落,而不是可以处理任意大量文本的基于 BM25 的技术。 现在&…

Linux CentOS 8(DHCP的配置与管理)

Linux CentOS 8(DHCP的配置与管理) 目录 一、项目介绍二、DHCP服务简介三、DHCP工作原理四、配置DHCP服务4.1 项目配置准备4.2 dhcpd配置文件框架与参数说明4.3 登录客户机验证4.4 客户端IP地址的释放与重新申请4.5 保留特定IP地址 一、项目介绍 当计算…

使用Open3D库处理3D模型数据的实践指南

目录 引言 一、安装Open3D库 二、加载3D模型数据 三、处理3D模型数据 1、去除模型中的无效面 2、提取模型特征 四、存储处理后的3D模型数据 五、可视化处理后的3D模型数据 六、注意事项 结论 引言 在处理3D模型数据时,Open3D库是一个功能强大且易于使用的…

【Qt之QStandardItemModel】使用,tableview、listview、treeview设置模型

1. 引入 QStandardItemModel类提供了一个通用的模型,用于存储自定义数据。 以下是其用法:该类属于gui模块,因此在.pro中,需添加QT gui,如果已存在,则无需重复添加。 首先,引入头文件&#xff…

ARM 版 Kylin V10 部署 KubeSphere 3.4.0 不完全指南

前言 知识点 定级:入门级KubeKey 安装部署 ARM 版 KubeSphere 和 KubernetesARM 版麒麟 V10 安装部署 KubeSphere 和 Kubernetes 常见问题 实战服务器配置 (个人云上测试服务器) 主机名IPCPU内存系统盘数据盘用途ksp-master-1172.16.33.1681650200KubeSphere/k8…

hash 哈希表

哈希表是一种期望算法。 一般情况下,哈希表的时间复杂度是 O(1)。 作用 将一个复杂数据结构映射到一个较小的空间 0~N(10^5~10^6),最常用的情景:将 0~10^9 映射到 0~10^5。 离散化是一种及其特殊的哈希方式。离散化…

【SAP-ABAP】--MRKO隐式增强字段步骤

业务需求:给MRKO增加几个增强字段 给标准表进行增强 1.如果标准表或者结构,带CL_***,一般表示SAP预留的增强位置,可以 直接双击这个类型,点击创建,然后直接在预留的结构里面添加自己 需要增加的字段 2.如…

无线物理层安全大作业

这个标题很帅 Beamforming Optimization for Physical Layer Security in MISO Wireless NetworksProblem Stateme![在这里插入图片描述](https://img-blog.csdnimg.cn/58ebb0df787c4e23b0c7be4189ebc322.png) Beamforming Optimization for Physical Layer Security in MISO W…

wpf devexpress Property Grid创建属性定义

WPF Property Grid控件使用属性定义定义如何做和显示 本教程示范如何绑定WP Property Grid控件到数据和创建属性定义。 执行如下步骤 第一步-创建属性定义 添加PropertyGridControl组件到项目。 打开工具箱在vs,定位到DX.23.1: Data 面板,选择Prope…