MySQL索引优化案例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 开发同学或多或少会遇到系统响应慢的问题,除了业务系统本身的问题外,常常会遇到SQL查询慢的问题,这篇文章结合实际案例分析MySQL InnoDB存储引擎的索引优化,这篇文章不会介绍B+树的知识点,如果需要了解聚集索引和辅助索引特点的同学可以参考这篇文章,这篇文章主要会介绍三星索引和ICP优化.

开发同学或多或少会遇到系统响应慢的问题,除了业务系统本身的问题外,常常会遇到SQL查询慢的问题,这篇文章结合实际案例分析MySQL InnoDB存储引擎的索引优化,这篇文章不会介绍B+树的知识点,如果需要了解聚集索引和辅助索引特点的同学可以参考这篇文章,这篇文章主要会介绍三星索引和ICP优化.

如何分析SQL性能
首先是查看MySQL的状态,系统是否正常,常用的几个命令如下:

显示状态信息(扩展show status like ‘XXX’)

Mysql> show status;

显示系统变量(扩展show variables like ‘XXX’)

Mysql> show variablesG;

显示InnoDB存储引擎的状态

Mysql> show engine innodb statusG;

查看当前SQL执行,包括执行状态、是否锁表等

Mysql> show processlist ;
第二步是找出系统有哪些慢查询SQL,这个要通过slowLog来查询,首先开启慢查询日志,然后在对应日志路径找到mysql-slow.log,相关命令如下所示:

检查是否开启慢查询日志

show variables like '%slow%';

如果没有开启,也可以在运行时动态开启这个参数

set global slow_query_log=ON;

设置慢查询记录查询耗时多长的SQL,这里设置成100毫秒

set long_query_time = 0.1;

这里休眠500毫秒试一下慢查询日志是否会记录

select sleep(0.5)
找到了慢SQL后比较常见的做法就是用explain命令分析SQL执行计划,查看SQL语句是否命中了索引,explain的用法可以参考MySQL 性能优化神器 Explain 使用分析,在优化过程中我们可能需要看到优化前后的查询时间对比,这时候可以打开profiling开关,查看某条SQL语句的执行耗时情况,分析是哪个步骤耗时较长,相关设置如下:

查看是否开启profiling

select @@profiling;

开profiling,注意测试完关闭该特性,否则耗费资源

set profiling=1;

查看所有记录profile的SQL

show profiles;

查看指定ID的SQL的详情

show profile for query 1;

测试完,关闭该特性

set profiling=0;
一个profiling例子:

profiling
三星索引优化策略
三星索引的策略是根据查询语句来建立联合索引,比如有这样一条SQL,SELECT GroupId,AddTime,Status from Order WHERE GroupId = 10010 ORDER BY AddTime,如何用三星索引来优化这条查询呢?

一星索引的核心就是利用索引来尽可能的过滤不必要的数据,减少数据处理的规模,对于RDBMS来说是极为关键的,比如说Order表有100万行数据,GroupId的过滤度(cardinality)是90%,Status的过滤度是0.1%,,如果没有建立索引,那要扫描100万行数据去找到GroupId等于10010的那一条数据,因此这里需要把GroupId作为联合索引的第一列,你是不是加了GroupId的索引就解决问题了呢,答案是否定的,虽然GroupId索引过滤了大部分数据,但由于select 后面有order by语句,而且这条SQL查询的不止GroupId这一个字段,不可避免要二次IO,而且还要在内存里进行一次filesort,explain的执行结果如下所示:
explain

从上图可以看到这条SQL使用了GroupId这个索引,但是Extra里面并不是直接Using Index,而是Using Index Condition,这意味着通过索引无法完成这条查询,存储引擎还是得读取对应的记录来完成查询,不过这里使用了ICP技术把GroupId这个索引下推到存储引擎层进行过滤,而且在内存中进行了一次filesort排序.
这里就要用到二星索引来优化filesort了,filesort一般出现在要排序的字段没有添加索引或者即使添加了索引但索引不是有顺序的情况下,需要在内存中进行一次排序然后再返回给客户端,如果数据量比较大的情况下排序的开销也挺大,二星索引基本的想法就是利用索引的有序性,消除orderby或者group by等需要排序的操作(备注:group by语句默认会对该字段排序),排序是非常消耗CPU资源的,大量的排序操作会把user cpu搞得很高,即使CPU吃得消,如果数据量比较大,需要排序的数据放不下内存的sort buffer,只能悲剧的和外存换进换出,性能下降的就不是一点两点了,这时候利用索引避免排序的优势就明显的体现出来了,这时候就需要建立GroupId和AddTime的联合索引了,当GroupId相等的时候联合索引的第二列AddTime就默认是有顺序的,这样就可以避免filesort,修改索引后的效果如下所示:
explain
从上面的图片可以看到filesort已经没有了,但是还是使用ICP而不是Using Index,这是因为我们Select的字段还包含Status这一列,而这一列不在联合索引中,因此存储引擎还是需要读取该行记录来获取Status的值,这时候三星索引就派上用场了, 在索引中额外添加要查询的列Status,这就是所谓的索引覆盖,即在索引的叶子节点就能够读到查询SQL所需要的所有信息,而不需要回原表去查询,如下图所示:
explain

这里的Extra列显示是Using Where,Using Index,意思是数据是从索引里面取,不需要回表,Using where表示需要根据其他列来过滤数据,从key_len列也看出索引的长度为5,表示只用了GroupId这个索引,索引长度的计算可以参考这篇文章
除了order by,group by的字段适合作为二星索引外,范围查询(包括between and)也适合,但是当order by和范围查询同时存在时,就需要根据实际情况作出取舍了,看是filesort的开销比较大还是范围查询的开销大

关于三星索引,并不是说要把select语句里所有的查询字段都加到索引列,索引字段太多会给数据库带来很大的开销,特别是会影响DML语句的性能,而且还要结合cardinality字段来分析,有些字段的取值范围很小,选择度很低,不适合作为索引.

ICP技术介绍
前面的章节多次提到了ICP技术,那到底什么是ICP呢,在介绍ICP之前我们先来看一下SQL语句的where条件提取规则:所有SQL的where条件,都可以归纳为3大类:Index Key,Index Filter和Table Filter,下面的介绍以CREATE TABLE Order (GroupId int(11) DEFAULT NULL, AddTime datetime DEFAULT NULL, Status int(11) DEFAULT NULL,KEY IX_GroupId (GroupId), KEY IX_AddTime (AddTime)) ENGINE=InnoDB这个表结构为例,假设我们分别单独建立了groupId和AddTime的索引,假设我们要执行这样一条SQLselect groupid,addtime,status from Order where groupid < 10010 and addTime > '1970' and status = 0;,那SQL执行计划会是怎样呢?

Index Key
Index Key只是用来定位索引的起止范围,因此只在索引第一次搜索时使用,一次判断即可;

从起始范围之后读到的每一条索引记录,均需要判断是否在Index Key的范围内,以上面的SQL为例,Index Key就是GroupId,它的范围是GroupId < 10010.

Index Filter
用于过滤索引查询范围中不满足查询条件的记录,因此对于索引范围中的每一条记录,均需要与Index Filter进行对比,若不满足Index Filter则直接丢弃,继续读取索引下一条记录,在前面的SQL中,Index Filter就是AddTime > '1970',MySQL会把这个过滤条件下推到存储引擎层,ICP就是Index Condition Push Down,不管是Index Key还是Index Filter对应的列必须添加了索引.

Table Filter
Table Filter是Where条件最后一道防线,用于过滤通过前面索引的层层考验的记录,此时的记录已经满足了Index Key的范围,并且符合Index Filter的条件,存储引擎通过回表读取了完整的记录,判断整条记录是否满足Table Filter中的查询条件,同样的,若不满足,跳过当前记录,继续读取索引的下一条记录,若满足,则返回记录,此记录满足了where的所有条件,可以返回给前端用户.还是回到前面那条SQL,Where条件中的Status=0就是Table Filter,因为Status列并没有添加索引,所以只能通过回表读取该字段来过滤.

我们来总结下ICP的过程,MySQL把Index Filter条件AddTime>'1970'下推到存储引擎,存储引擎根据AddTime索引来过滤掉不符合where条件AddTime>'1970'的记录,然后把最终数据返回给MySQL Server,MySQL Server再根据where条件status=0来做最后的过滤,最后把数据返回给用户,这个过程减少了二次回表的次数,有效减少了磁盘IO.最后我们来看下explain语句的结果:

Table Filter
其他优化建议
整型数据比起字符,处理开销更小,在MySQL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;
尽量指定列为NOT NULL,除非你想存储NULL,在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂,你应该用0、一个特殊的值或者一个空串代替空值;
当结果集只需要一行数据时使用LIMIT 1
避免SELECT *,始终指定你需要的列
使用连接(JOIN)来代替子查询(Sub-Queries),使用Join来代理大的分页语句,比如select ... Limit 1000000,10 这条语句MySQL要根据查询条件找到符合的100万条记录,然后删掉再读取后10条记录,应该根据Where条件把主键查出来,然后根据主键去访问数据,比如select * from Order o inner join (select Id from Order where ...) t where o.id=t.id limit 1000000,10;
where子句的查询条件里有!=,MySQL将无法使用索引
使用LIKE进行搜索匹配的时候,这样索引是有效的:select * from Order where name like ‘xxx%’,而like ‘%xxx%’` 时索引无效

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
5月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
5月前
|
存储 关系型数据库 MySQL
MySQL数据库索引的数据结构?
MySQL中默认使用B+tree索引,它是一种多路平衡搜索树,具有树高较低、检索速度快的特点。所有数据存储在叶子节点,非叶子节点仅作索引,且叶子节点形成双向链表,便于区间查询。
180 4
|
7月前
|
存储 关系型数据库 MySQL
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
|
6月前
|
SQL 缓存 关系型数据库
MySQL 慢查询是怎样优化的
本文深入解析了MySQL查询速度变慢的原因及优化策略,涵盖查询缓存、执行流程、SQL优化、执行计划分析(如EXPLAIN)、查询状态查看等内容,帮助开发者快速定位并解决慢查询问题。
249 0
|
3月前
|
SQL 关系型数据库 MySQL
Mysql数据恢复—Mysql数据库delete删除后数据恢复案例
本地服务器,操作系统为windows server。服务器上部署mysql单实例,innodb引擎,独立表空间。未进行数据库备份,未开启binlog。 人为误操作使用Delete命令删除数据时未添加where子句,导致全表数据被删除。删除后未对该表进行任何操作。需要恢复误删除的数据。 在本案例中的mysql数据库未进行备份,也未开启binlog日志,无法直接还原数据库。
|
4月前
|
缓存 关系型数据库 MySQL
降低MySQL高CPU使用率的优化策略。
通过上述方法不断地迭代改进,在实际操作中需要根据具体场景做出相对合理判断。每一步改进都需谨慎评估其变动可能导致其他方面问题,在做任何变动前建议先在测试环境验证其效果后再部署到生产环境中去。
206 6
|
5月前
|
存储 SQL 关系型数据库
MySQL 核心知识与索引优化全解析
本文系统梳理了 MySQL 的核心知识与索引优化策略。在基础概念部分,阐述了 char 与 varchar 在存储方式和性能上的差异,以及事务的 ACID 特性、并发事务问题及对应的隔离级别(MySQL 默认 REPEATABLE READ)。 索引基础部分,详解了 InnoDB 默认的 B+tree 索引结构(多路平衡树、叶子节点存数据、双向链表支持区间查询),区分了聚簇索引(数据与索引共存,唯一)和二级索引(数据与索引分离,多个),解释了回表查询的概念及优化方法,并分析了 B+tree 作为索引结构的优势(树高低、效率稳、支持区间查询)。 索引优化部分,列出了索引创建的六大原则
134 2
|
6月前
|
存储 关系型数据库 MySQL
MySQL覆盖索引解释
总之,覆盖索引就像是图书馆中那些使得搜索变得极为迅速和简单的工具,一旦正确使用,就会让你的数据库查询飞快而轻便。让数据检索就像是读者在图书目录中以最快速度找到所需信息一样简便。这样的效率和速度,让覆盖索引成为数据库优化师傅们手中的尚方宝剑,既能够提升性能,又能够保持系统的整洁高效。
167 9
|
5月前
|
存储 SQL 关系型数据库
MySQL 动态分区管理:自动化与优化实践
本文介绍了如何利用 MySQL 的存储过程与事件调度器实现动态分区管理,自动化应对数据增长,提升查询性能与数据管理效率,并详细解析了分区创建、冲突避免及实际应用中的关键注意事项。
198 0
|
7月前
|
存储 SQL 关系型数据库
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?