四、MySQL Explain 工具

本文深入探讨了MySQL的Explain关键字,用于分析查询性能。通过示例展示了如何创建和使用Explain,以及如何解读其输出结果,包括id、select_type、table、type、possible_keys、key、key_len、ref、rows和Extra列。重点讲解了不同类型的查询类型、访问类型及其意义,以及如何利用慢查询日志进行性能调优。
摘要由CSDN通过智能技术生成

一、Explain 介绍

  • 使用 Explain 关键字。
    可以 模拟优化器 执行 SQL语句,分析你的 查询语句 或是 结构 的性能瓶颈。
  • 在 select 语句之前,增加 explain 关键字。
  1. MySQL 会在查询上设置一个标记。
  2. 执行查询 会 返回执行计划 的信息,而不是 执行这条SQL。
  • 注意:如果 from 中包含子查询,仍会执行该子查询,将结果放入临时表中。

二、Explain 示例


1. 创建数据库

SHOW DATABASES;

CREATE DATABASE learn_mysql;

USE learn_mysql;

2. 创建数据表

  • 男演员。
DROP TABLE IF EXISTS `actor`;

CREATE TABLE `actor` (
	`id` int(11) NOT NULL,
	`name` varchar(45) DEFAULT NULL,
	`update_time` datetime DEFAULT NULL,
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 插入三条数据
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES 
(1, 'a', '2017-12-22 15:27:18'), 
(2, 'b', '2017-12-22 15:27:18'), 
(3, 'c', '2018-12-22 15:27:18');

  • 电影。
DROP TABLE IF EXISTS `film`;

CREATE TABLE `film` (
	`id` int(11) NOT NULL AUTO_INCREMENT,
	`name` varchar(10) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `film` (`id`, `name`) VALUES 
(3, 'film0'), 
(1, 'film1'), 
(2, 'film2');

  • 电影演员。
DROP TABLE IF EXISTS `film_actor`;

CREATE TABLE `film_actor` (
	`id` int(11) NOT NULL,
	`film_id` int(11) NOT NULL,
	`actor_id` int(11) NOT NULL,
	`remark` varchar(255) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `idx_film_actor_id` (`film_id`,`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`) VALUES 
(1, 1, 1), 
(2, 1, 2), 
(3, 2, 1);

3. explain

explain select * from actor;

EXPLAIN format=json select * from actor;

在这里插入图片描述

  1. 在查询中的每个表会输出一行。
  2. 如果有两个表通过 join 连接查询,那么会输出两行。

三、Explain 扩展


1. explain extended 扩展

  • explain extended
  1. 在 explain 的基础上,额外提供一些 查询优化的信息。
  2. 紧随其后通过 show warnings 命令,可以得到优化后的查询语句。
  3. 额外有 filtered 列,是一个百分比的值。
    rows * filtered / 100 可以估算出,将要和 explain 中前一个表进行 连接的行数。
    前一个表指 explain 中的 id值 比当前表 id值 小的表。
explain extended select * from film where id = 1;

在这里插入图片描述


  • show warnings
    可以得到优化后的查询语句,从而看出优化器优化了什么。
explain extended select * from film where id = 1;
show warnings;

在这里插入图片描述


2. explain partitions 分区

  • explain partitions
  1. 相比 explain 多了个 partitions(分区字段)。
  2. 如果查询是基于分区表的话,会显示查询 将访问的分区。

四、Explain 中的列


1. id 执行顺序

  • id列 是 select 的序列号(执行顺序的标识):
    表示有几个 select 查询,并且 id列 是按 select 出现的顺序增长的。
  1. id列 越大,执行优先级越高。
  2. id列 相同,则从上往下执行。
  3. id列 为 NULL,最后执行。

2. select_type 查询类型(6种)

  • select_type列 表示 查询类型:
  1. SIMPLE 简单查询。
  2. PRIMARY 主查询。
  3. SUBQUERY 子查询(select)。
  4. DERIVED 子查询(from)。
  5. UNION 并集查询(union)。
  6. UNION RESULT。

2.1 SIMPLE 简单查询
  1. 简单 select 查询。
  2. 查询不包含 子查询、联合查询 或 union。
explain select * from film where id = 2;

在这里插入图片描述


2.2 PRIMARY 主查询
  1. 最外层的 select。
  2. 如果有子查询的话,最外层的查询会被标记为 PRIMARY。
2.3 SUBQUERY 子查询(select)
  1. 包含在 select 中的 子查询,不在 from 子句中。
-- 子查询。
explain select (select 1 from actor where id = 1) 
-- 复杂查询,
from (select * from film where id = 1) der;

在这里插入图片描述


2.4 DERIVED 子查询(from)
  1. 包含在 from 子句中的 子查询。
  2. 生成的临时表的查询语句,会被标记为 DERIVED。
# 关闭`MySQL-5.7`新特性,对衍生表的合并优化。
set session optimizer_switch='derived_merge=off';

explain select (select 1 from actor where id = 1) 
from (select * from film where id = 1) der;

# 还原默认配置。
set session optimizer_switch='derived_merge=on'; 
  • MySQL 会将结果存放在一个临时表中,也称为 派生表(derived 的英文含义)。
    在这里插入图片描述

2.5 UNION 并集查询(union)
  1. 在 union 中 第二个 和 随后的select。
explain select 1 
union all select 1;

在这里插入图片描述


2.6 UNION RESULT
  1. 从 UNION 语句中获取结果。
EXPLAIN SELECT * FROM table1 
UNION SELECT * FROM table2;

3. table 查询的表(3种)

  • 表示 explain 时,正在访问的表
  1. 当 from子句 中有 子查询 时,table列是 <derivenN> 格式。
    表示 当前查询 依赖 id=N 的查询结果,于是先执行 id=N 的查询。
  2. 当 union 查询时,union 结果的 table列 值为 <union1,2>
    1 和 2 分别表示参与的 select 行id。

  • 需求:在索引列中取最小值。
    可以 单独查找索引 来完成,不需要在执行时 访问表。
explain select min(id) from film;

在这里插入图片描述

  1. table = NULL
    MySQL 能够在优化阶段 分解查询语句,在 执行阶段 用不着再 访问表 或 索引。

4. type 访问类型(7种)【重要】

  • SQL 性能的基本情况,表示 关联类型访问类型
    即 MySQL 决定 如何查找表中的行,查找 数据行记录 的大概范围。
  • 性能从最优到最差分别为:
    system > const > eq_ref > ref > range > index > ALL
  • 正常保证查询达到 range级,最好可以达到 ref级

4.1 system 级
  1. system 是 const 的特例。
  2. 表中只有一条记录匹配时,为 system。

4.2 const 级
  1. 通过索引一次就找到了数据。
    一般使用了 primary key列 或 unique key列,条件是与 常量 比较时(字符串、数字)。
  2. 表中最多有一个匹配行,只读取 1次 速度比较快。
explain extended 
select * from (select * from film where id = 1) tmp;

在这里插入图片描述

  1. MySQL 能对 查询的某部分 进行优化,并将其转化成一个常量。
    可以看 show warnings 的结果。

show warnings
explain extended select * from (select * from film where id = 1) tmp;
show warnings;

在这里插入图片描述


4.3 eq_ref 级
  1. 使用了主键索引,或者非空唯一索引。
    在表中 只有一条记录 与 索引键相匹配,匹配条件是某个表的列(需要转义替换才能拿到值)。
  2. 用于 primary key列 或 unique key列 的所有部分。
    被连接使用时,最多只会返回一条符合条件的记录。

const 和 eq_ref 的区别
  • eq_ref 是在 const 之外最好的连接类型了,简单的 select 查询不会出现。
  1. const 和 eq_ref 两个都是在用到了 主键索引 或 唯一索引 的情况下出现。
  2. 不同的是 const 的 where 条件是常量。
  3. eq_ref 的 where 条件是其他表的某个列,需要对这个列进行转义才能拿到匹配条件的值。
  4. 也可以简单的理解为,eq_ref 一般为关联查询。
explain select * from film_actor 
-- `eq_ref`级。
left join film on film_actor.film_id = film.id;

在这里插入图片描述


4.4 ref 级
  1. 非唯一性索引扫描。
    和 eq_ref 不同的是 eq_ref 匹配的是唯一索引。
    ref 它返回所有匹配某个单独值的行,它可能会找到多个符合条件的行。
  2. 相比 eq_ref 不使用 唯一索引。
    而是使用 普通索引 或 唯一索引的部分前缀。
  • 索引要和某个值相比较,可能会找到多个符合条件的行。

  1. 简单 select 查询,name 是普通索引(非唯一索引)。
explain select * from film where name = 'film1';

在这里插入图片描述


  1. 关联表查询。
explain select film_id from film 
left join film_actor on film.id = film_actor.film_id;
  1. idx_film_actor_id 是 film_id 和 actor_id 的联合索引。
  2. 这里使用到了 film_actor 的 左边前缀 film_id 部分。
    在这里插入图片描述

4.5 range 级,范围查询
  1. 通常出现在 inbetween ... and ...><>= 等操作中。
  2. 使用一个索引,来检索给定 范围的行。
explain select * from actor where id > 1;

在这里插入图片描述


4.6 index 级,扫描全表索引
  1. 全索引扫描,通过扫描整棵索引树获取到结果。
  2. 通常比 ALL 快一些。
explain select * from film;

在这里插入图片描述


4.7 ALL 级,即全表扫描
  1. 意味着 MySQL 需要 从头到尾 去查找所需要的行。
  2. 通常这种情况下,需要增加索引来进行优化了。
explain select * from actor;

在这里插入图片描述


5. possible_keys 可能使用到的索引【重要】

  • 查询时 可能使用到的索引:
  1. explain 查询时,possible_keys列 有值,而 key列 显示 NULL 的情况。
    这种情况是因为表中数据不多,MySQL 认为索引对此查询帮助不大。
    选择了全表查询。
  2. 如果该列是 NULL,则没有可用到的索引。
    在这种情况下,可以检查 where 子句。
    看是否可以创建一个适当的索引,来提高查询性能,然后用 explain 查看效果。

6. key 使用到的索引【重要】

  • 查询时 实际采用到的索引:
  1. 如果没有使用索引,则该列是 NULL。
  2. 如果想强制 MySQL 使用 或 忽视 possible_keys列 中的索引。
    在查询中使用 force indexignore index

7. key_len 使用到的索引长度

  • 使用到的索引 key 的长度:
  1. 显示 MySQL 在索引里,使用到的字节数。
    根据这个值可以计算出,具体使用到了 索引中的哪些列。
  2. 如果为 联合索引,则显示已命中的联合索引长度之和。
    如:联合索引为a+b+c ,如果索引命中了a+b,那么长度就为a+b的索引长度。
  3. 通常可以通过 key_len列,来分析联合索引所命中的情况。
explain select * from film_actor where film_id = 2;

在这里插入图片描述

  1. 通过结果中的 key_len=4 可推断出。
    查询使用了 film_id列 来执行索引查找。
  • 例如:film_actor 的联合索引 idx_film_actor_id。
    由 film_id 和 actor_id 两个 int 列组成,并且每个 int 是 4字节。

7.1 key_len 计算规则如下
  • 字符串:
  1. char(n):n个字节长度。
  2. varchar(n):2个字节存储字符串的长度。
    例如:如果是utf-8,则具体长度 3n + 2。

  • 数值类型:
  1. tinyint:1个字节。
  2. smallint:2个字节。
  3. int:4个字节。
  4. bigint:8个字节。

  • 时间类型:
  1. date:3个字节。
  2. timestamp:4个字节。
  3. datetime:8个字节。

  • 如果字段允许为 NULL,需要 1个字节 记录是否为 NULL。

  • 索引 最大长度是 768 个字节:
  1. 当字符串过长时,MySQL 会做一个类似左前缀索引的处理。
  2. 将前半部分的字符,提取出来做索引。

7.2 关于 possible_keys 和 key 的三种关系场景
  1. possible_keys != null && key !=null,这是正常使用到了索引的情况。
  2. possible_keys !=null && key == null,这种情况一般说明通过索引并不能提升多少效率。
    一般而言是表的数据量很少,或者是索引的字段离散性不高,执行计划发现用索引和扫表差不多。
  3. possible_keys == null && key !=null,这种情况一般为 where 条件没有命中索引。
    但是查询的列是索引字段,也就是查询的列命中覆盖索引情况。

8. ref 查询的列或常量

  • 显示在 key列 记录的索引中,表查询时所用到的 列 或 常量。
  1. 常见的有:const(常量)、字段名(例:film.id)。
  2. 实际用到的索引是哪个表的列,const 代表常量。

9. rows 扫描的行数

  • 显示 MySQL 预计要读取并检索的行数。
  1. 注意这个不是结果集里的行数。
  2. 一般来说扫描的数据行数越少,性能越好。

10. Extra 扩展信息【重要】

  • Extra 是额外信息:对整个 SQL做了一个概括性的总结
    包括使用了 什么索引、排序方式、使用了临时表,主要不适合在其他列显示,但是十分重要的信息。
  1. Using index:使用覆盖索引。
  2. Using where:使用 where 条件语句来处理结果。
  3. Using index condition:查询的列不完全被索引覆盖。
  4. Using temporary:MySQL 需要创建一张 临时表 来处理查询。
  5. Using filesort:使用外部文件排序,而不是索引排序。
  6. Select tables optimized away:使用某些聚合函数。
  7. Using index sort:使用索引排序。
    通常情况下这是一种好现象,索引天然有序,所以避免了通过 sort buffer 来排序的流程。
  8. Using join buffer:使用了 join buffer 缓存。
    这种情况关注一下关联查询的字段,是不是没有建索引。

10.1 Using index 使用覆盖索引
  1. 通常情况下这是一种好现象,意味着查询的数据直接在二级索引返回了,从而减少了回表的过程。
explain 
select film_id from film_actor where film_id = 1;

在这里插入图片描述


10.2 Using where 使用 where 语句来处理结果
  1. 查询的列未被索引覆盖。
explain select * from actor where name = 'a';

在这里插入图片描述


10.3 Using index condition 查询的列不完全被索引覆盖
  1. where 条件中是一个前导列的范围。
explain select * from film_actor where film_id > 1;

在这里插入图片描述


10.4 Using temporary MySQL 需要创建一张 临时表 来处理查询
  1. 出现这种情况,一般是要进行优化的,首先考虑使用索引来优化
  2. 常见于 group by、order by。
explain 
select distinct name from actor;
  1. actor.name 没有索引。
    此时创建了张 临时表 来 distinct 去重。
    在这里插入图片描述

explain select distinct name from film;
  1. film.name 有 idx_name 索引。
    此时查询时,Extra 是 using index 没有用临时表。
    在这里插入图片描述

10.5 Using filesort 使用外部文件排序,而不是索引排序
  1. 数据较小时在 内存排序,否则需要在 磁盘完成排序。
    这种情况,也是 需要考虑使用索引来优化的
  2. 文件排序,就是使用了 非索引的字段 进行排序。
explain 
select * from actor order by name;
  1. actor.name 没有索引。
    会检索整个 actor 表,保存排序关键字 name 和 对应的行id。
    然后 排序name 并检索行记录。在这里插入图片描述

explain 
select * from film order by name;
  1. film.name 有 idx_name 索引。
    此时查询时,Extra 是 using index(索引排序)。
    在这里插入图片描述

10.6 Select tables optimized away 使用某些聚合函数
  1. 如:max、min 来访问 存在索引的某个字段。
explain select min(id) from film;

在这里插入图片描述


五、MySQL 慢查询日志

  • MySQL 慢查询日志 默认不开启。
  • 开启后 MySQL 会把【查询时间 大于 设置时间的SQL记录下来】,并保存到一个专门的文件中。

1. 配置——慢查询日志

  • 在 MySQL 配置文件中(/etc/my.conf)进行配置,修改后重启 MySQL 生效。
# `开启或关闭`慢查询日志。
slow_query_log=ON
# 指定生成慢查询日志路径(未设置则默认和数据文件放一起)。
slow_query_log_file=/opt/soft/mysql/log/slow.log
# 慢查询记录时间阈值,SQL执行超过此时间则会被记录到日志(单位秒,默认10秒)。
long_query_time=5
# 是否记录未使用到索引的SQL(尽管这条SQL语句有可能执行得挺快)。
log_queries_not_using_indexes=on

show VARIABLES like '%query%';

# 0_OFF、1_ON
set global slow_query_log=1;
# 1秒。
set long_query_time=1;

在这里插入图片描述


2. 分析——慢查询日志

  • MySQL 提供了【分析慢查询日志的工具】mysqldumpslow。
# 查询【返回结果最多】的10条SQL。
mysqldumpslow -s r -t 10 /usr/local/mysql/data/localhost_slow.log

# 查询【耗时最长】的10条SQL。
mysqldumpslow -s t -t 10 /usr/local/mysql/data/localhost_slow.log
参数描述
-s是 sort 的意思,表示按照何种方式排序。
c 访问次数、l 锁定时间、r 返回记录数、t 查询时间
ac 平均访问次数、al 平均锁定时间、ar 平均返回记录数、at 平均查询时间。
-t是 top ‘n’ 的意思,即为返回前面多少条的数据。
-g是 grep 的意思,后边可以写一个正则匹配模式,大小写不敏感的。

3. 其他语句——慢查询日志

  • 默认情况下 管理语句 是不会被记录到 慢查询日志 中。
  • 管理语句包括 ALTER TABLE、ANALYZE TABLE、CHECK TABLE、CREATE INDEX、DROP INDEX、OPTIMIZE TABLE 和 REPAIR TABLE 等。
show VARIABLES like '%log_slow_admin_statements%';

set global log_slow_admin_statements=ON;

在这里插入图片描述


  • 默认情况下,不使用索引的语句,也是不会被记录的。
show VARIABLES like '%log_queries_not_using_indexes%';

set global log_queries_not_using_indexes=ON;

在这里插入图片描述


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值