一、前言
前段时间自己做项目,项目用到了MySQL技术栈,主要是用MySQL记录任务信息,后面在压测的时候,发现一个接口的QPS始终上不去,后来经过排查发现,该接口在运行的过程中使用了慢查询语句,通过排查慢查询语句,最后发现是没有对字段建立联合索引,在建立了联合索引之后,性能提升了将近两倍。此文记录自己踩过的一些坑,以及解决方案。
二、如何找到慢SQL语句
MySQL提供了慢查询日志,可以帮助我们找到慢查询语句。
2.1 什么是慢查询日志?
MySQL的慢询日志,可以记录在MySQL中,响应时间超过指定阈值的语句,比如设定阈值为3秒,那么任何SQL执行超过3秒都会被记录下来。
我们借助慢查询日志功能可以发现哪些那些执行时间特别长的询,并且有针对性地进行优化,从而提高系统的整体效率。
2.2 怎么开启慢查询日志
慢查询日志一般默认是关闭的,因为开启慢查询日志会对性能造成一定的影响,我们只需要在性能调优的时候开启就可以了,所以慢查询日志需要我们自己去开启。
执行下面命令查看是否开启慢SQL:
如果是off就是没开启,是on就是开启了。
如何通过命令开启慢查询日志?
ini
set global slow_query_log='ON';
2.3 如何设置慢查询日志的阈值
ini
set global long_query_time = N;
这里的单位是秒,设置为N就是N秒,所有执行时间超过N秒的sql语句都会被记录在慢查询日志里。
2.4 如何查看慢查询日志的内容
先找到慢查询日志的位置:
sql
show variables like '%slow_query_log_file%';
查看日志内容可以使用cat命令来查看:
matlab
cat xxx.log
三、explain分析工具的使用
explain是MySQL自带的执行分析工具,把他加在sql语句前面,就可以展示出该sql语句的具体的执行计划。
执行计划是按照多个字段来展示的,我们平时在调优的时候主要关注以下几个关键字段:type,possible keys,key,rows,filtered,extra。下面我会一一介绍这些字段的具体含义。
3.1 type字段
type字段表示当前语句所执行的类型,有如下几种类型:
-
all
:全表扫描,基于表中所有的数据,逐行扫描并过滤符合条件的数据。 -
index
:全索引扫描,和全表扫描类似,但这个是把索引树遍历一次,会比全表扫描要快。 -
range
:基于索引字段进行范围查询 ,如between、<、>、in....
等操作时出现的情况。 -
index_subquery
:和上面含义相同,区别:这个是基于非主键、唯一索引字段进行in
操作。 -
unique_subquery
:执行基于主键索引字段,进行in
操作的子查询语句会出现的情况。 -
index_merge
:多条件查询时,组合使用多个索引来检索数据的情况。 -
ref_or_null
:基于次级(非主键)索引做条件查询时,该索引字段允许为null
出现的情况。 -
fulltext
:基于全文索引字段,进行查询时出现的情况。 -
ref
:基于非主键或非唯一索引字段查找数据时,会出现的情况。 -
eq_ref
:连表查询 时,基于主键、唯一索引字段匹配数据的情况,会出现多次索引查找。 -
const
:通过索引一趟查找后就能获取到数据 ,基于唯一、主键索引字段查询数据时的情况。 -
system
:表中只有一行数据,这是const
的一种特例。 -
null
:表中没有数据,无需经过任何数据检索,直接返回结果。
字段很多,常用的有下面几个,性能由好到坏依次是:const->eq_ref->ref->fulltext->range->index->all
平时索引优化,最差也要优化到range,如果是index甚至是all,是不允许的,太慢了。
3.2 key字段
possible keys表示可以使用的索引,key是最终优化器选择使用的索引。注意,并不是有索引优化器就会选择使用索引,优化器会估算查询的cost,选择最低的那个查询方式。如果索引的基数太大,也有可能不会选用,因为使用二级索引意味着要回表,回表也是一个很大的开销。
默认情况下,possible_keys
有值时都会从中选取一个索引,但这个选择的工作是由MySQL
优化器自己决定的,如果你想让查询语句执行时走固定的索引,则可以通过force index、ignore index
的方式强制指定。
3.3 rows字段
这一列代表执行时,预计会扫描的行数 ,这个数字是估算的,所以并不是十分精确,但也具备很大的参考价值,如果这个值很大,在执行查询语句时,其效率必然很低,所以该值越小越好。
3.4 filtered 字段
这个字段仅作参考。filter字段的含义是,存储引擎返回的数据在server层过滤后,剩下的满足查询条件的记录数量占存储引擎返回数据的百分比 ,因此这个值越大说明过滤掉的越少,就"越好"。但显然,filtered如果很小,只能说明被过滤的多了,并不一定说明索引不好或者引擎效率不高,所以这个值实质上参考意义不大。
3.5 extra字段
extra包含的是额外的信息,但是这个信息对sql优化是很重要的。
但是这个字段的值有很多很多,下面列举了一些:
-
Using index
:表示目前的查询语句,使用了索引覆盖机制拿到了数据。 -
Using where
:表示目前的查询语句无法从索引中获取数据,需要进一步做回表去拿表数据,也就是回表。通常出现这种情况的话就需要添加合适的索引来做优化了。 -
Using temporary
:表示MySQL
在执行查询时,会创建一张临时表来处理数据,MySQL需要创建一个临时表来保存结果。经常出现在查询包含以不同方式列出列的GROUP BY和ORDER BY子句的情况。 -
Using filesort
:表示MySQL会对结果使用一个外部索引排序,而不是从表里按索引次序读到相关内容。可能在内存或者磁盘上进行排序。 MySQL中无法利用索引完成的排序操作称为"文件排序" -
Select tables optimized away
:表示查询过程中,对于索引字段使用了聚合函数。 -
Using where;Using index
:表示要返回的数据在索引中包含,但并不是索引的前导列,需要做回表获取数据。 -
NULL
:表示查询的数据未被索引覆盖,但where
条件中用到了主键,可以直接读取表数据。 -
Using index condition
:和Using where
类似,要返回的列未完全被索引覆盖,需要回表。会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。 -
Using join buffer (Block Nested Loop)
:连接查询时驱动表不能有效的通过索引加快访问速度时,会使用join-buffer
来加快访问速度,在内存中完成Loop
匹配。 -
Impossible WHERE
:where
后的条件永远不可能成立时提示的信息,如where 1!=1
。 -
Impossible WHERE noticed after reading const tables
:基于唯一索引查询不存在的值时出现的提示。 -
const row not found
:表中不存在数据时会返回的提示。 -
distinct
:去重查询时,找到某个值的第一个值时,会将查找该值的工作从去重操作中移除。 -
Start temporary, End temporary
:表示临时表用于DuplicateWeedout
半连接策略,也就是用来进行semi-join
去重。 -
Using MRR
:表示执行查询时,使用了MRR
机制读取数据。 -
Using index for skip scan
:表示执行查询语句时,使用了索引跳跃扫描机制读取数据。 -
Using index for group-by
:表示执行分组或去重工作时,可以基于某个索引处理。 -
FirstMatch
:表示对子查询语句进行Semi-join
优化策略。 -
No tables used
:查询语句中不存在from
子句时提示的信息,如desc table_name;
通常情况下当Extra中出现了Using filesort、Using temporary或Using where,则说明你写的SQL需要优化了。
四、项目中遇到的问题
本项目是一个异步任务处理框架,其中服务模块提供了一个从MySQL数据库占据任务的一个接口。占据任务的时候,是根据任务的状态和优先级先进行排序,再拉取指定数量的任务的。
任务的状态分为待执行和执行中,以及成功和失败,拉取任务的接口只能拉取待执行的任务,在本项目中,我用的是status关键字来记录任务的状态。在任务状态为待执行的任务里,我们还要对优先级排序字段进行排序,优先级越大的,越有可能被拉去到,越有可能被最先执行。
一开始的时候,我对status字段和优先级字段分别建立了索引,但是忘记了建立联合索引,这个时候hold_task接口去拉取任务的时候,就会比较慢,因为数据量还是比较大的,有数十万。
后来,压测的时候发现hold_task的QPS始终不是很高,于是就对该接口进行检查,通过慢查询日志,定位到了慢sql语句,就是select xx from table where status = "待执行" ORDERED BY order_time(伪代码) 。然后对这个sql语句使用explain关键字进行分析,发现type字段是index_merge
,extra字段的值是Using where;Using index
,没有走联合索引,而是索引合并,而且需要回表。
于是再回去检查MySQL表之后发现,忘记建立联合索引了,于是对status字段和优先级字段建立了联合索引,这样一来就避免了回表查询。建立联合索引之后再使用explain关键字进行分析的时候就发现,type字段变成了ref ,extra字段的值变成了Using index,使用了覆盖索引,再进行压测,QPS就增大了一倍多。