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

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ROS 学习应用篇(八)ROS中的坐标变换管理之tf广播与监听的编程实现

偶吼吼胜利在望,冲冲冲 老规矩新建功能包 工作空间目录下/src下开启终端输入 catkin_create_pkg learning_tf roscpp rospy tf turtlesim 如何实现tf广播 引入库 c python …

小米真无线耳机 Air 2s产品蓝牙配对ubuntu20.04 笔记本电脑

小米真无线耳机 Air 2s产品蓝牙配对ubuntu20.04 笔记本电脑 1.我的笔记本是 22款联想拯救者y9000k,安装了双系统,ubuntu20.04。 2.打开耳机,按压侧面按钮2秒,指示灯显示白色闪烁。 3.打开ubunru20.04 系统右上角wifi的位置&…

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

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

大数据可视化是什么?

大数据可视化是将海量数据通过视觉方式呈现出来,以便于人们理解和分析数据的过程。它可以帮人们发现数据之间的关系、趋势和模式,并制定更明智的决策。大数据可视化通常通过图形、图表、地图和仪表盘等视觉元素来呈现数据。这些元素具有直观、易理解的特…

常见的反爬+文字加解密

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

CF1899A Game with Integers(思维题)

题目链接 题目 题目大意 t 组测试样例 每组给一个正整数 n, 有两种操作: 1-1 A 和 B 轮流操作, 如果这个整数变成了一个能被3整除的数,A赢,输出First 如果在10次操作以内,n不能被3整数,B赢&…

CF1899B 250 Thousand Tons of TNT

题目链接 题目 题目大意 T T T 组测试数据 每组 n n n 个货物,第 i i i 个货物 的重量是 a i a_i ai​ 用k辆货车按顺序装这些货物,条件是每辆车上的货物个数都一样,也即是说 n n n 必须能被 k k k 整除, 求任意两辆车货物总…

一起学docker系列之五docker的常用命令--操作容器的命令

目录 前言1 启动容器2 查看容器3 退出容器4 启动已经停止的容器5 重启容器6 停止容器7 删除已经停止的容器8 启动容器说明和举例9 查看容器日志10 查看容器内运行的进程11 查看容器内部细节12 进入正在运行的容器并进行交互13 导入和导出容器结语 前言 当涉及到容器化技术&…

如何在远程协同视频会议中确保安全性?

随着远程工作的普及,远程协同视频会议已成为企业和团队之间进行交流和协作的重要工具。与此同时,会议中的安全性问题也日益凸显。本文将介绍如何在远程协同视频会议中确保安全性,主要包括以下方面: 1、内网部署 将会议服务器部署…

python引入自己不同目录的模块

1.目录结构 from manual_data.utils import delete_and_insert_center

【Proteus仿真】【STM32单片机】公交车报站系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器,使用LCD12864显示模块、DS18B20温度传感器、DS1302时钟模块、按键、LED蜂鸣器、ULN2003、28BYJ48步进电机模块等。 主要功能: 系统运行…

学习css过渡动画-transition

文章目录 前言transition属性语法宽度改变效果透明度改变效果位置改变效果如有启发,可点赞收藏哟~ 前言 通常,当一个元素的样式属性值发生变化时,会立即看到页面发生变化。 css属性transition能让页面元素不是立即的、而是慢慢的从一种状态变…