MySQL性能分析工具的使用

性能分析工具的使用

在数据库调优中,我们的目标就是响应时间更快吞吐量更大。利用宏观的监控工具和微观的日志分析可以帮我们快速找到调优的思路和方式。

数据库服务器的优化步骤

当我们遇到数据库调优问题的时候,该如何思考呢?这里把思考的流程整理成下面这张图。

整个流程划分成了观察(Show status)行动(Action) 两个部分。字母 S 的部分代表观察(会使
用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)。

MySQL性能分析工具的使用_第1张图片

如果A2和A3都不能解决问题,我们需要考虑数据库自身的SQL查询性能是否已经达到了瓶颈,如果确认没有达到性能瓶颈,就需要重新检查,重复以上的步骤。如果已经达到了性能瓶颈,进入A4阶段,需要考虑增加服务器,采用读写分离的架构,或者考虑对数据库进行分库分表,比如垂直分库、垂直分表和水平分表等。

以上就是数据库调优的流程思路。如果我们发现执行SQL时存在不规则延迟或卡顿的时候,就可以采用分析工具帮我们定位有问题的SQL,这三种分析工具你可以理解是SQL调优的三个步骤:慢查询EXPLAINSHOWPROFILING

小结:

MySQL性能分析工具的使用_第2张图片

查看系统性能参数

在MySQL中,可以使用SHOW STATUS语句查询一些MysQL数据库服务器的性能参数执行频率。SHoW STATUS语句语法如下:

SHOW STATUS语句语法如下:

SHOW [GLOBAL | SESSION ] STATUS LIKE '参数';

一些常用的性能参数如下:

  • Connections:连接MySQL服务器的次数。
show status like 'Connections';
  • Uptime:MySQL服务器的上线时间。
show status like 'Uptime';
  • Slow_queries:慢查询的次数。
show status like 'Slow_queries';
  • Innodb_rows_read:Select查询返回的行数
show status like 'Innodb_rows_read';
  • Innodb_rows_inserted:执行INSERT操作插入的行数
show status like 'Innodb_rows_inserted';
  • Innodb_rows_updated:执行UPDATE操作更新的行数
show status like 'Innodb_rows_updated';
  • Innodb_rows_deleted:执行DELETE操作删除的行数
show status like 'Innodb_rows_deleted';
  • Com_select:查询操作的次数。
show status like 'Com_select';
  • Com_insert:插入操作的次数。对于批量插入的 INSERT 操作,只累加一次。
show status like 'Com_insert';
  • Com_update:更新操作的次数。
show status like 'Com_update';
  • Com_delete:删除操作的次数 。
show status like 'Com_delete';

慢查询次数参数可以结合慢查询日志找出慢查询语句,然后针对慢查询语句进行表结构优化或者查询语句优化。再比如,如下的指令可以查看相关的指令情况:

SHOW STATUS LIKE 'Innodb_rows_%';

统计SQL的查询成本:last_query_cost

一条SQL查询语句在执行前需要确定查询执行计划,如果存在多种执行计划的话,MySQL会计算每个执行计划所需要的成本,从中选择成本最小的一个作为最终执行的执行计划。

如果我们想要查看某条SQL语句的查询成本,可以在执行完这条SQL语句之后,通过查看当前会话中的
last_query_cost变量值来得到当前查询的成本。它通常也是我们评价一个查询的执行效率的一个常用指标。这个查询成本对应的是 SQL语句所需要读取的页的数量

我们依然使用第8章的 student_info 表为例:

CREATE TABLE `student_info` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`student_id` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`course_id` INT NOT NULL ,
`class_id` INT(11) DEFAULT NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

查看最后一条语句花费的成本

show status like 'last_query_cost';

下面的意思就是最后一次的查询大概需要做一页的随即查找。

image-20220322170848898

然后再看下查询优化器的成本,这时我们大概需要进行 2249个页的查询。(4999 rows in set (0.04 sec))

image-20220322171836084

然后再看下查询优化器的成本,这时我们大概需要进行218个页的查询。(484 rows in set (0.00 sec))

image-20220322172020756

上面两个查询之间页的数量相差十倍,查询效率并没有明显的变化,两条SQL语句的时间基本上一样,就是因为采取了顺序读取的方式一次性加载到缓冲池中,然后再进行查找。虽然也数量增加了不少,但是通过缓冲池的机制,并没有增加多少时间。(回忆第07章_InnoDB数据存储结构,区、段、页)

定位执行慢的SQL:慢查询日志

MySQL的慢查询日志,用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time默认是10,意思是运行10秒以上(不含10秒)的语句,认为是超出了我们的最大忍耐时间值。

它的主要作用是,帮助我们发现那些执行时间特别长的SQL查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合explain进行全面分析。

默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。

慢查询日志支持讲日志记录写入文件。

开启慢查询日志参数

  1. 开启slow_query_log

在使用前,我们需要先看下慢查询是否已经开启,使用下面这条命令即可:

show variables like '%slow_query_log' ;

image-20220322175401977

开启慢查询日志

set global slow_query_log='ON';

查看慢查询日志以及慢查询日志文件所在位置

show variables like '%slow_query_log%' ;

  1. 修改long_query_time阈值

接下来我们来看下慢查询的时间阈值设置,使用如下命令:

show variables like '%long_query_time%';

image-20220322175915508

这里如果我们想把时间缩短,比如设置为 1 秒,可以这样设置: long_query_time既是global参数,也是session参数

#测试发现:设置global的方式对当前session的long_query_time失效。对新连接的客户端有效。所以可以一并执行下述语句
set global long_query_time = 1;
show global variables like '%long_query_time%';
set long_query_time=1;
show variables like '%long_query_time%';
  1. 永久修改,设置慢查询日志时间

修改my.cnf(默认在/etc/my.cnf),在[mysqld]下增加或修改参数long_query_timeslow_query_log.file后重启服务器。

[mysqld]
slow_query_log=ON #开启慢查询日志的开关
slow_query_log.file=/var/lib/mysql/atguigu-slow.log #慢查询日志的目录和文件名信息long_query_time=3 #设置慢查询的阈值为3秒,超出此设定值的SQL即被记录到慢查询日志
log_output=FILE

如果不指定存储路径,慢查询日志将默认存储到MySQL数据库的数据文件夹下。如果不指定文件名,默认文件名为hostname-slow.log。

补充说明:

除了上述变量,控制慢查询日志的还有一个系统变量: min_examined_row_limit。这个变量的意思是,查询扫描过的最少记录数。这个变量和查询执行时间,共同组成了判别一个查询是否是慢查询的条件。如果查询扫描过的记录数大于等于这个变量的值,并且查询执行时间超过long_query_time的值,那么,这个查询就被记录到慢查询日志中;反之,则不被记录到慢查询日志中。

mysql> show variables like 'min%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| min_examined_row_limit | 0     |
+------------------------+-------+
1 row in set (0.00 sec)

这个值默认是0。与long_query_time=10合在一起,表示只要查询的执行时间超过10秒钟(我设置的慢查询日志的时间是0.5秒),哪怕一个记录也没有扫描过,都要被记录到慢查询日志中。你也可以根据需要,通过修改“my.ini"文件,来修改查询时长,或者通过SET指令,用SQL语句修改“min_examined_row_limit”的值。

慢查询日志分析工具:mysqldumpslow

在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow

查看mysqldumpslow的帮助信息。

mysqldumpslow --help

MySQL性能分析工具的使用_第3张图片

查看慢查询日志

mysqldumpslow -a -s t -t 5 /var/lib/mysql/localhost-slow.log 

按照时间从高到低,取前面5条查询语句。(删除慢查询日志之后,需要重新开启才会有慢查询日志出现)

MySQL性能分析工具的使用_第4张图片

关闭慢查询日志

MySQL服务停止慢查询日志功能有两种方式:

方式一:永久性方式

修改my.cnf或者my.ini文件,把[mysqld]组下的slow_query_log值设置为OFF,修改保存后,再重启MysQL服务,即可生效;

[mysqld]
slow_query_log=OFF

或者,把slow_query_log一项注释掉 或 删除

[mysqld]
#slow_query_log =OFF

重启MySQL服务,执行如下语句查询慢日志功能。

SHOW VARIABLES LIKE '%slow%'; #查询慢查询日志所在目录
SHOW VARIABLES LIKE '%long_query_time%'; #查询超时时长

方式二:临时性的方式

使用SET语句来设置

(1)停止MySQL慢查询日志功能,具体SQL如下。

SET GLOBAL slow_query_log =off;

(2)重启MySQL服务,使用SHOW语句查询慢查询日志功能信息,具体SQL语句如下

SHOW VARIABLES LIKE '%slow%';
#以及
SHOW VARIABLES LIKE '%long_query_time%';

删除慢查询日志

使用SHOW语句显示慢查询日志信息,具体SQL语句如下。

SHOW VARIABLES LIKE 'slow_query_log%';

image-20220322212203283

从执行结果可以看出,慢查询日志的目录默认为MySQL的数据目录,在该目录下手动删除慢查询日志文件即可。

使用命令mysqladmin flush-logs来重新生成查询日志文件,具体命令如下,执行完成会在数据目录下重新生成慢查询日志文件。(需要重新开始才会出现)

mysqladmin -uroot -p flush-logs slow

查看SQL执行成本:SHOW PROFILE(查看之前的即可)

show profile在《逻辑架构》章节中讲过,这里作为复习。

Show Profile是MySQL提供的可以用来分析当前会话中SQL都做了什么、执行的资源消耗情况的工具,可用于sql调优的测量。默认情况下处于关闭状态,并保存最近15次的运行结果。

我们可以在会话级别开启这个功能

查看是否以及开启profiling

show variables like 'profiling';

开启profiling

set profiling =1;
或者set profiling = 'on';

分析查询语句:EXPLAIN

概述

**定位了查询慢的SQL之后,我们就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句。**DESCRIBE语句的使用方法与EXPLAIN语句是一样的,并且分析结果也是一样的。

MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)。

这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。MySQL为我们提供了EXPLAIN语句来帮助我们查看某个查询语句的具体执行计划,大家看懂EXPLAIN语句的各个输出项,可以有针对性的提升我们查询语句的性能。

  1. 能做什么?

    • 表的顺序读取操作
    • 数据读取操作类型
    • 哪些索引可以使用
    • 哪些索引可以被实际使用
    • 表之间的引用
    • 每张表有多少行数据被优化器查询
  2. MySQL官网

基本语法

EXPLAIN或DESCRIBE语法的语法形式如下:

EXPLAIN SELECT  [选项]DESCRIBE SELECT  [选项] 

如果我们想看看某个查询的执行计划的话,可以在具体的查询语句前边加一个EXPLAIN,就像这样:

EXPLAIN SELECT 1;
mysql> EXPLAIN SELECT 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

输出的上述信息就是所谓的执行计划。在这个执行计划的辅助下,我们需要知道应该怎样改进自己的查询语句以使查询执行起来更高效。其实除了以SELECT开头的查询语句,其余的DELETEINSERTREPLACE以及UPDATE语句等都可以加上EXPLAIN,用来查看这些语句的执行计划,只是平时我们对SELECT语句更感兴趣。

注意:执行EXPLAIN时并没有真正的执行该后面的语句,因此可以安全的查看执行计划。

EXPLAIN语句输出的各个列的作用如下:

image-20220323121725436

列表 描述
id 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
select_type SELECT关键字对应的那个查询的类型
table 表名
possible_keys 可能用到的索引
key 实际上使用的索引
key_len 实际使用到的索引长度
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比
Extra —些额外的信息

在这里把它们都列出来只是为了描述一个轮廓,让大家有一个大致的印象。

数据准备

  1. 建表
CREATE TABLE s1 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;
CREATE TABLE s2 (
id INT AUTO_INCREMENT,
key1 VARCHAR(100),
key2 INT,
key3 VARCHAR(100),
key_part1 VARCHAR(100),
key_part2 VARCHAR(100),
key_part3 VARCHAR(100),
common_field VARCHAR(100),
PRIMARY KEY (id),
INDEX idx_key1 (key1),
UNIQUE INDEX idx_key2 (key2),
INDEX idx_key3 (key3),
INDEX idx_key_part(key_part1, key_part2, key_part3)
) ENGINE=INNODB CHARSET=utf8;

table:表名

不论我们的查询语句有多复杂,里边儿包含了多少个表 ,到最后也是需要对每个表进行单表访问的,所
以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该
表的表名(有时不是真实的表名字,可能是简称)。(有几张表就代表有几条记录)

#查询的每一行记录都对应着一个单表
EXPLAIN SELECT * FROM s1;

image-20220323130528901

id

我们写的查询语句一般都以SELECT关键字开头,比较简单的查询语句里只有一个SELECT关键字,比
如下边这个查询语句:

一个select对应着一个id

#s1:驱动表  s2:被驱动表
EXPLAIN SELECT * FROM s1 INNER JOIN s2;

image-20220323213930793

 ######查询优化器可能对涉及子查询的查询语句进行重写,转变为多表查询的操作########
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');

image-20220323130619417

小结:

  • id如果相同,可以认为是一组,从上往下顺序执行
  • 在所有组中,id值越大,优先级越高,越先执行
  • 关注点:id号每个号码,表示一趟独立的查询, 一个sql的查询趟数越少越好

select_type

一条大的查询语句里边可以包含若干个SELECT关键字,每个SELECT关键字代表着一个小的查询语句,而每个SELECT关键字的FROM子句中都可以包含若干张表(这些表用来做连接查询),每一张表都对应着执行计划输出中的一条记录,对于在同一个SELECT关键字中的表来说,它们的id值是相同的。

MySQL为每一个SELECT关键字代表的小查询都定义了一个称之为select_type的属性,意思是我们只要知道了某个小查询的select_type属性,就知道了这个小查询在整个大查询中扮演了一个什么角色,我们看一下select_type都能取哪些值,请看官方文档:

名称 JSON Name 描述
SIMPLE None Simple SELECT (not using UNION or subqueries)
PRIMARY None Outermost SELECT
UNION None Second or later SELECT statement in a UNION
DEPENDENT UNION dependent (true) Second or later SELECT statement in a UNION, dependent on outer query
UNION RESULT union_result Result of a UNION.
SUBQUERY None First SELECT in subquery
DEPENDENT SUBQUERY dependent (true) First SELECT in subquery, dependent on outer query
DERIVED None Derived table
DEPENDENT DERIVED dependent (true) Derived table dependent on another table
MATERIALIZED materialized_from_subquery Materialized subquery
UNCACHEABLE SUBQUERY cacheable (false) A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query
UNCACHEABLE UNION cacheable (false) The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY)

其实上面都有英文的解释,我下面用中文解释加例子来表示,可以详细看看上面的表,单词其实很简单。

**SIMPLE:**简单的 select 查询,查询中不包含子查询或者 union
**PRIMARY:**查询中包含子部分,最外层查询则被标记为 primary
**SUBQUERY/MATERIALIZED:**SUBQUERY 表示在 select 或 where 列表中包含了子查询,**MATERIALIZED:**表示 where 后面 in 条件的子查询
**UNION:**表示 union 中的第二个或后面的 select 语句
UNION RESULT: union 的结果

  • 查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型
 EXPLAIN SELECT * FROM s1;

image-20220323215122287

这就是上面PRIMARY和UNIO的解释

  • 对于包含UNION或者UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type值就是PRIMARY
  • 对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type值就是UNION
 EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

最下面那个是要去重,使用了临时表

image-20220323215624237

子查询

不相关子查询:子查询的查询条件不依赖于父查询,称为不相关子查询。

例:查询与"刘晨"在同一个系学习的学生。

SELECT Sno,Sname,Sdept
FROM Student
wHERE Sdept IN
(SELECT Sdept 
FROM Student
WhERE Sname='刘晨');

相关子查询:如果子查询的查询条件依赖于父查询,这类子查询称为相关子查询。

例:找出每个学生超过他自己选修课程的平均成绩的课程号。

SELECT Sno,Cno
FROM SC x
WHERE Grade >= (SELECT AVG(Grade)
               FROM SC y
               WHERE y.Sno=x.Sno);

求解相关子查询不能像不相关子查询那样一次性将子查询求解出来,然后求解父查询。内查询由于与外层查询有关,因此必须反复求值。

  • 如果包含子查询的查询语句不能够转为对应的semi-join(多表连接)的形式,并且该子查询是不相关子查询。该子查询的第一个SELECT关键字代表的那个查询的select_type就是SUBQUERY
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';

image-20220323222233755

  • 如果包含子查询的查询语句不能够转为对应的semi-join(多表连接)的形式,并且该子查询是相关子查询,则该子查询的第一个SELECT关键字代表的那个查询的select_type就是DEPENDENT SUBQUERY(会查询多次)

in在优化器底层会把in改造成exists,改成相关子查询所以下面就会加上DEPENDENT

 EXPLAIN SELECT * FROM s1 
 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a';

image-20220323223334631

  • 对于包含派生表的查询,该派生表对应的子查询的select_type就是DERIVED
 EXPLAIN SELECT * 
 FROM (SELECT key1, COUNT(*) AS c FROM s1 GROUP BY key1) AS derived_s1 WHERE c > 1;

image-20220324123511138

  • 当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type属性就是MATERIALIZED(物化的)
 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2); #子查询被转为了物化表

partitions (可略)

  • 代表分区表中的命中情况,非分区表,该项为NULL。一般情况下我们的查询语句的执行计划的partitions列的值都是NULL
  • https://dev.mysql.com/doc/refman/5.7/en/alter-table-partition-operations.html

type⭐

执行计划的一条记录就代表着MySQL对某个表的执行查询时的访问方法,又称“访问类型”,其中的type列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到type列的值是ref,表明MySQL即将使用ref访问方法来执行对s1表的查询。

级别由高到低。

完整的访问方法如下:systemconsteq_refreffulltextref_or_nullindex_mergeunique_subqueryindex_subqueryrangeindexALL

我们详细解释一下:

s1表的表结构

MySQL性能分析工具的使用_第5张图片

  • system

当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的(底层有一个变量去记录COUNT(*)),比如MyISAM、Memory,那么对该表的访问方法就是system

CREATE TABLE t(i INT) ENGINE=MYISAM;
INSERT INTO t VALUES(1);
EXPLAIN SELECT * FROM t;

image-20220324125623366

换成InnoDB之后type就变成了All

image-20220324130001701

  • const

当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const

 EXPLAIN SELECT * FROM s1 WHERE id = 10005;

image-20220324130312888

当where后面那id用了函数就会造成索引失效,因为key3是varchar类型的,这儿输入的是数字,会存在隐式换。

EXPLAIN SELECT * FROM s1 WHERE key3 = 10066;

image-20220324132720737

  • eq_ref

在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnSOlp2r-1648126870199)(https://cdn.jsdelivr.net/gh/losserlong/pictures/20220324205310.png)]

  • ref

当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref

 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTUJ9ScR-1648126870200)(../AppData/Roaming/Typora/typora-user-images/image-20220324131910834.png)]

  • ref_or_null

当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null

image-20220324134143584

  • index_merge

单表访问方法时在某些场景下可以使用IntersectionUnionSort-Union这三种索引合并的方式来执行查询,查询优化器会将两个索引合并成一个索引。

image-20220324135157813

mysql>  EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
+----+-------------+-------+------------+-------------+-------------------+-------------------+---------+------+------+----------+---------------------------------------------+
| id | select_type | table | partitions | type        | possible_keys     | key               | key_len | ref  | rows | filtered | Extra                                       |
+----+-------------+-------+------------+-------------+-------------------+-------------------+---------+------+------+----------+---------------------------------------------+
|  1 | SIMPLE      | s1    | NULL       | index_merge | idx_key1,idx_key3 | idx_key1,idx_key3 | 303,303 | NULL |    2 |   100.00 | Using union(idx_key1,idx_key3); Using where |
+----+-------------+-------+------------+-------------+-------------------+-------------------+---------+------+------+----------+---------------------------------------------+
1 row in set, 1 warning (0.00 sec)

  • unique_subquery

unique_subquery是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换为EXISTS子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的type列的值就是unique_subquery

 EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 =
s2.key1) OR key3 = 'a';

image-20220324141516429

  • range

如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法

 EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');

image-20220324141913359

 EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
  • index

当我们可以使用索引覆盖(因为这里在查询的时候查询的字段正好式索引字段的一部分,不需要回表操作),但需要扫描全部的索引记录时,该表的访问方法就是index

 EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bEh6wcyx-1648126870201)(C:/Users/losser/AppData/Roaming/Typora/typora-user-images/image-20220324142809584.png)]

image-20220324142739683

  • All

最熟悉的全表扫描

explain select * from s1;

image-20220324143044882

小结:
结果值从最好到最坏依次是: system > const > eq_ref > ref> fulltext > ref_or_null > index_merge >unique_subquery > index_subquery > range > index > ALL 其中比较重要的几个提取出来(见上图中的蓝色)。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴开发手册要求)

possible_keys和key

在EXPLAIN语句输出的执行计划中,possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key列表示实际用到的索引有哪些,如果为NULL,则没有使用索引。比方说下边这个查询:

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';

image-20220324144147984

key_len⭐

key_len:实际使用到的索引长度(即:字节数)帮你检查是否充分的利用上了索引值越大越好,主要针对于联合索引,有一定的参考意义。

#varchar(10)变长字段且允许NULL  = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
EXPLAIN SELECT * FROM s1 WHERE id = 10005;

MySQL性能分析工具的使用_第6张图片

 EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';

image-20220324180415440

 EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';

image-20220324180451308

ref

当使用索引列等值查询时,与索引列进行等值匹配的对象信息。比如只是一个常熟或者是某个列。

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

MySQL性能分析工具的使用_第7张图片

 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvAFc5t9-1648126870204)(https://cdn.jsdelivr.net/gh/losserlong/pictures/20220324205401.png)]

 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1lh4Mf6x-1648126870204)(https://cdn.jsdelivr.net/gh/losserlong/pictures/20220324205405.png)]

rows⭐

rows:预估的需要读取的记录数,值越小越好

 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';

image-20220324183215363

filtered

filtered:某个表经过搜索条件过滤后剩余记录条数的百分比(比例越高越好)

如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用到对应索引的搜索条件外的其他搜索条件(也就是下面的common_field = ‘a’)的记录有多少条。

 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';

这下面的filtered的意思是在412rows里面只有10%符合条件,因为还要通过common_field='a';进行过滤

image-20220324190320848

对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即:rows * filtered)

 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';

9895*10%

image-20220324193956311

Extra⭐

顾名思义,Extra列是用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。MySQL提供的额外信息有好几十个,我们就不一个一个介绍了,所以我们只挑比较重要的额外信息介绍给大家。

  • No tables used

当查询语句的没有FROM子句时将会提示该额外信息,比如:

EXPLAIN SELECT 1;

image-20220324194341864

  • Impossible WHERE

查询语句的WHERE子句永远为FALSE时将会提示该额外信息

 EXPLAIN SELECT * FROM s1 WHERE 1 != 1;

image-20220324194507442

  • Using where

当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。

 EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';

image-20220324194647439

当使用索引访问来执行对某个表的查询,并且该语句的WHERE子句中有除了该索引包含的列之外的其他搜索条件时,在Extra列中也会提示上述额外信息。

 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';

image-20220324194805988

  • No matching min/max row

当查询列表处有MIN或者MAX聚合函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息

 EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';

image-20220324195012552

  • Select tables optimized away (选择优化掉的表)

zzXpJl是 s1表中key1字段真实存在的数据

 EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'zzXpJl'
  • Using index(使用上了索引,这种是很好的)

当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,在Extra列将会提示该额外信息。比方说下边这个查询中只需要用到idx_key1不需要回表(回忆,因为二级索引只存了主键值和这个列的值)操作:

 EXPLAIN SELECT key1,id FROM s1 WHERE key1 = 'a';

image-20220324195850049

  • Using index condition

有些搜索条件中虽然出现了索引列,但却不能使用到索引看课件理解索引条件下推

 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';

上面的SQL语句分析:

  • 先根据key1 > 'z'这个条件,定位到二级索引idx_key1中对应的二级索引记录。
  • 对于指定的二级索引记录,先不着急回表,而是先检测一下该记录是否满足key1 LIKE '%a'这个条件,如果这个条件不满足,则该二级索引记录压根儿就没必要回表。
  • 对于满足key1 LIKE '%a'这个条件的二级索引记录执行回表操作

我们说回表操作其实是一个随机IO,比较耗时,所以上述修改虽然只改进了一点点,但是可以省去好多回表操作的成本。MySQL把他们的这个改进称之为索引条件下推(英文名: Index Condition Pushdown)。如果在查询语句的执行过程中将要使用索引条件下推这个特性,在Extra列中将会显示Using index condition,比如这样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LV9CYSBl-1648126870207)(https://cdn.jsdelivr.net/gh/losserlong/pictures/20220324205438.png)]

  • Using join buffer (hash join)

在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法

 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
  • Not exists

当我们使用左(外)连接时,如果WHERE子句中包含要求被驱动表的某个列等于NULL值的搜索条件,而且那个列又是不允许存储NULL值的,那么在该表的执行计划的Extra列就会提示Not exists额外信息

 EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;

image-20220324201949146

  • Using union(idx_key1,idx_key3);

如果key1和key2都普通索引的话使用了or就会将两个索引进行合并

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';

image-20220324203507257

  • Using filesort使用文件排序

这个查询语句可以利用idx_key1索引直接取出key1列的10条记录,然后再进行回表操作就好了。但是很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:filesort)。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示Using filesort提示,比如这样:

 EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;

MySQL性能分析工具的使用_第8张图片

  • Using temporary

在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重排序之类的,比如我们
在执行许多包含DISTINCTGROUP BYUNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra列将会显示Using temporary提示

 EXPLAIN SELECT DISTINCT common_field FROM s1;

image-20220324204320772

执行计划中出现Using temporary并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以我们最好能使用索引来替代掉使用临时表。比如:扫描指定的索引idx_key1即可

小结

  • EXPLAIN不考虑各种Cache
  • EXPLAIN不能显示MySQL在执行查询时所作的优化工作
  • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
  • 部分统计信息是估算的,并非精确值

你可能感兴趣的:(mysql,mysql,数据库)