在数据库调优中,我们的目标就是响应时间更快
,吞吐量更大
。利用宏观的监控工具和微观的日志分析可以帮我们快速找到调优的思路和方式。
当我们遇到数据库调优问题的时候,该如何思考呢?这里把思考的流程整理成下面这张图。
整个流程划分成了观察(Show status)
和 行动(Action)
两个部分。字母 S 的部分代表观察(会使
用相应的分析工具),字母 A 代表的部分是行动(对应分析可以采取的行动)。
如果A2和A3都不能解决问题,我们需要考虑数据库自身的SQL查询性能是否已经达到了瓶颈,如果确认没有达到性能瓶颈,就需要重新检查,重复以上的步骤。如果已经达到了性能瓶颈
,进入A4阶段,需要考虑增加服务器
,采用读写分离
的架构,或者考虑对数据库进行分库分表
,比如垂直分库、垂直分表和水平分表等。
以上就是数据库调优的流程思路。如果我们发现执行SQL时存在不规则延迟或卡顿的时候,就可以采用分析工具帮我们定位有问题的SQL,这三种分析工具你可以理解是SQL调优的三个步骤:慢查询
、EXPLAIN
和SHOWPROFILING
。
小结:
在MySQL中,可以使用SHOW STATUS
语句查询一些MysQL数据库服务器的性能参数
、执行频率
。SHoW STATUS语句语法如下:
SHOW STATUS语句语法如下:
SHOW [GLOBAL | SESSION ] STATUS LIKE '参数';
一些常用的性能参数如下:
show status like 'Connections';
show status like 'Uptime';
show status like 'Slow_queries';
show status like 'Innodb_rows_read';
show status like 'Innodb_rows_inserted';
show status like 'Innodb_rows_updated';
show status like 'Innodb_rows_deleted';
show status like 'Com_select';
show status like 'Com_insert';
show status like 'Com_update';
show status like 'Com_delete';
慢查询次数参数可以结合慢查询日志找出慢查询语句,然后针对慢查询语句进行表结构优化或者查询语句优化。再比如,如下的指令可以查看相关的指令情况:
SHOW STATUS LIKE 'Innodb_rows_%';
一条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';
下面的意思就是最后一次的查询大概需要做一页的随即查找。
然后再看下查询优化器的成本,这时我们大概需要进行 2249
个页的查询。(4999 rows in set (0.04 sec)
)
然后再看下查询优化器的成本,这时我们大概需要进行218
个页的查询。(484 rows in set (0.00 sec)
)
上面两个查询之间页的数量相差十倍,查询效率并没有明显的变化,两条SQL语句的时间基本上一样,就是因为采取了顺序读取的方式一次性加载到缓冲池中,然后再进行查找。虽然也数量增加了不少,但是通过缓冲池的机制,并没有增加多少时间。(回忆第07章_InnoDB数据存储结构
,区、段、页)
MySQL的慢查询日志,用来记录在MySQL中响应时间超过阀值
的语句,具体指运行时间超过long_query_time
值的SQL,则会被记录到慢查询日志中。long_query_time默认是10
,意思是运行10秒以上(不含10秒)的语句,认为是超出了我们的最大忍耐时间值。
它的主要作用是,帮助我们发现那些执行时间特别长的SQL查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合explain进行全面分析。
默认情况下,MySQL数据库没有开启慢查询日志
,需要我们手动来设置这个参数。如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。
慢查询日志支持讲日志记录写入文件。
在使用前,我们需要先看下慢查询是否已经开启,使用下面这条命令即可:
show variables like '%slow_query_log' ;
开启慢查询日志
set global slow_query_log='ON';
查看慢查询日志以及慢查询日志文件所在位置
show variables like '%slow_query_log%' ;
接下来我们来看下慢查询的时间阈值设置,使用如下命令:
show variables like '%long_query_time%';
这里如果我们想把时间缩短,比如设置为 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%';
修改my.cnf
(默认在/etc/my.cnf),在[mysqld]下增加或修改参数long_query_time
、slow_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”的值。
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow
。
查看mysqldumpslow的帮助信息。
mysqldumpslow --help
查看慢查询日志
mysqldumpslow -a -s t -t 5 /var/lib/mysql/localhost-slow.log
按照时间从高到低,取前面5条查询语句。(删除慢查询日志之后,需要重新开启才会有慢查询日志出现)
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%';
从执行结果可以看出,慢查询日志的目录默认为MySQL的数据目录,在该目录下手动删除慢查询日志文件
即可。
使用命令mysqladmin flush-logs
来重新生成查询日志文件,具体命令如下,执行完成会在数据目录下重新生成慢查询日志文件。(需要重新开始才会出现)
mysqladmin -uroot -p flush-logs slow
show profile在《逻辑架构》章节中讲过,这里作为复习。
Show Profile是MySQL提供的可以用来分析当前会话中SQL都做了什么、执行的资源消耗情况的工具,可用于sql调优的测量。默认情况下处于关闭状态
,并保存最近15次的运行结果。
我们可以在会话级别开启这个功能
查看是否以及开启profiling
show variables like 'profiling';
开启profiling
set profiling =1;
或者set profiling = 'on';
**定位了查询慢的SQL之后,我们就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句。**DESCRIBE语句的使用方法与EXPLAIN语句是一样的,并且分析结果也是一样的。
MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供它认为最优的执行计划
(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)。
这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。MySQL为我们提供了EXPLAIN
语句来帮助我们查看某个查询语句的具体执行计划,大家看懂EXPLAIN
语句的各个输出项,可以有针对性的提升我们查询语句的性能。
能做什么?
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
开头的查询语句,其余的DELETE
、INSERT
、REPLACE
以及UPDATE
语句等都可以加上EXPLAIN
,用来查看这些语句的执行计划,只是平时我们对SELECT语句更感兴趣。
注意:执行EXPLAIN时并没有真正的执行该后面的语句,因此可以安全的查看执行计划。
EXPLAIN
语句输出的各个列的作用如下:
列表 | 描述 |
---|---|
id |
在一个大的查询语句中每个SELECT关键字都对应一个唯一的id |
select_type |
SELECT关键字对应的那个查询的类型 |
table |
表名 |
possible_keys |
可能用到的索引 |
key |
实际上使用的索引 |
key_len |
实际使用到的索引长度 |
ref |
当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows |
预估的需要读取的记录条数 |
filtered |
某个表经过搜索条件过滤后剩余记录条数的百分比 |
Extra |
—些额外的信息 |
在这里把它们都列出来只是为了描述一个轮廓,让大家有一个大致的印象。
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;
不论我们的查询语句有多复杂,里边儿包含了多少个表
,到最后也是需要对每个表进行单表访问
的,所
以MySQL规定EXPLAIN语句输出的每条记录都对应着某个单表的访问方法,该条记录的table列代表着该
表的表名(有时不是真实的表名字,可能是简称)。(有几张表就代表有几条记录)
#查询的每一行记录都对应着一个单表
EXPLAIN SELECT * FROM s1;
我们写的查询语句一般都以SELECT
关键字开头,比较简单的查询语句里只有一个SELECT
关键字,比
如下边这个查询语句:
一个select对应着一个id
#s1:驱动表 s2:被驱动表
EXPLAIN SELECT * FROM s1 INNER JOIN s2;
######查询优化器可能对涉及子查询的查询语句进行重写,转变为多表查询的操作########
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');
小结:
一条大的查询语句里边可以包含若干个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;
这就是上面PRIMARY和UNIO的解释
UNION
或者UNION ALL
或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的select_type
值就是PRIMARY
UNION
或者UNION ALL
的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的select_type
值就是UNION
EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
最下面那个是要去重,使用了临时表
子查询
不相关子查询:子查询的查询条件不依赖于父查询,称为不相关子查询。
例:查询与"刘晨"在同一个系学习的学生。
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';
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';
派生表
的查询,该派生表对应的子查询的select_type
就是DERIVED
EXPLAIN SELECT *
FROM (SELECT key1, COUNT(*) AS c FROM s1 GROUP BY key1) AS derived_s1 WHERE c > 1;
select_type
属性就是MATERIALIZED
(物化的) EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2); #子查询被转为了物化表
NULL
。一般情况下我们的查询语句的执行计划的partitions
列的值都是NULL
。执行计划的一条记录就代表着MySQL
对某个表的执行查询时的访问方法
,又称“访问类型”,其中的type
列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到type
列的值是ref
,表明MySQL
即将使用ref
访问方法来执行对s1
表的查询。
级别由高到低。
完整的访问方法如下:system
,const
,eq_ref
,ref
,fulltext
,ref_or_null
,index_merge
,unique_subquery
,index_subquery
,range
,index
,ALL
。
我们详细解释一下:
s1表的表结构
当表中只有一条记录
并且该表使用的存储引擎的统计数据是精确的(底层有一个变量去记录COUNT(*)),比如MyISAM、Memory,那么对该表的访问方法就是system
。
CREATE TABLE t(i INT) ENGINE=MYISAM;
INSERT INTO t VALUES(1);
EXPLAIN SELECT * FROM t;
换成InnoDB之后type就变成了All
当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
EXPLAIN SELECT * FROM s1 WHERE id = 10005;
当where后面那id用了函数就会造成索引失效,因为key3是varchar类型的,这儿输入的是数字,会存在隐式换。
EXPLAIN SELECT * FROM s1 WHERE key3 = 10066;
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL
值时,那么对该表的访问方法就可能是ref_or_null
单表访问方法时在某些场景下可以使用Intersection
、Union
、Sort-Union
这三种索引合并的方式来执行查询,查询优化器会将两个索引合并成一个索引。
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
是针对在一些包含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';
如果使用索引获取某些范围区间
的记录,那么就可能使用到range
访问方法
EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
当我们可以使用索引覆盖(因为这里在查询的时候查询的字段正好式索引字段的一部分,不需要回表操作
),但需要扫描全部的索引记录时,该表的访问方法就是index
EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
最熟悉的全表扫描
explain select * from s1;
小结:
结果值从最好到最坏依次是: system > const > eq_ref > ref> fulltext > ref_or_null > index_merge >unique_subquery > index_subquery > range > index > ALL 其中比较重要的几个提取出来(见上图中的蓝色)。SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴开发手册要求)
在EXPLAIN语句输出的执行计划中,possible_keys
列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引
有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key
列表示实际用到的索引有哪些,如果为NULL,则没有使用索引。比方说下边这个查询:
EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
key_len:实际使用到的索引长度(即:字节数)帮你检查是否充分的利用上了索引
,值越大越好
,主要针对于联合索引,有一定的参考意义。
#varchar(10)变长字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
EXPLAIN SELECT * FROM s1 WHERE id = 10005;
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';
当使用索引列等值查询时,与索引列进行等值匹配的对象信息。比如只是一个常熟或者是某个列。
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);
rows:预估的需要读取的记录数,值越小越好
EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';
filtered:某个表经过搜索条件过滤后剩余记录条数的百分比(比例越高越好)
如果使用的是索引执行
的单表扫描,那么计算时需要估计出满足除使用到对应索引
的搜索条件外的其他搜索条件
(也就是下面的common_field = ‘a’)的记录有多少条。
EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';
这下面的filtered的意思是在412rows里面只有10%符合条件,因为还要通过common_field='a';
进行过滤
对于单表查询来说,这个filtered列的值没什么意义,我们更关注在连接查询中驱动表对应的执行计划记录的filtered值
,它决定了被驱动表要执行的次数(即:rows * filtered)
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
9895*10%
顾名思义,Extra
列是用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句
。MySQL提供的额外信息有好几十个,我们就不一个一个介绍了,所以我们只挑比较重要的额外信息介绍给大家。
No tables used
当查询语句的没有FROM子句时将会提示该额外信息,比如:
EXPLAIN SELECT 1;
Impossible WHERE
查询语句的WHERE
子句永远为FALSE
时将会提示该额外信息
EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
Using where
当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE
子句中有针对该表的搜索条件时,在Extra
列中会提示上述额外信息。
EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
当使用索引访问来执行对某个表的查询,并且该语句的WHERE
子句中有除了该索引包含的列之外的其他搜索条件时,在Extra
列中也会提示上述额外信息。
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
No matching min/max row
当查询列表处有MIN
或者MAX
聚合函数,但是并没有符合WHERE
子句中的搜索条件的记录时,将会提示该额外信息
EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
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';
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
,比如这样:
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;
Using union(idx_key1,idx_key3);
如果key1和key2都普通索引的话使用了or
就会将两个索引进行合并
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
Using filesort
使用文件排序这个查询语句可以利用idx_key1
索引直接取出key1
列的10条记录,然后再进行回表操作就好了。但是很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序
(英文名:filesort
)。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra
列中显示Using filesort
提示,比如这样:
EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10;
Using temporary
在许多查询的执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们
在执行许多包含DISTINCT
、GROUP BY
、UNION
等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,在执行计划的Extra
列将会显示Using temporary
提示
EXPLAIN SELECT DISTINCT common_field FROM s1;
执行计划中出现Using temporary
并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以我们最好能使用索引来替代掉使用临时表
。比如:扫描指定的索引idx_key1即可