1 准备
准备测试数据
为了便于理解,先创建两张测试表,建表及数据写入语句如下:
来源:MySQL数据库联盟
作者简介
马听,多年 DBA 实战经验,对MySQL、 Redis、MongoDB、Go等有一定了解,书籍《MySQL DBA精英实战课》作者,慕课网DBA体系课(https://class.imooc.com/sale/dba)讲师。
尽管现在很多SQL可以借助AI来优化,比如小编前面写的一篇文章:如何通过ChatGPT优化MySQL的SQL语句。
但是有些场景,可能ChatGPT并不能完全正确的优化,或者ChatGPT优化的SQL,需要我们再去校验,这个时候就需要用到执行计划的基本功了。
这一节我们就来详细介绍一下MySQL的执行计划工具explain。
Explain 可以获取 MySQL 优化器考虑使用的 SQL 语句执行计划,比如语句是否使用了关联查询、是否使用了索引、扫描行数等。可以帮我们选择更好地索引和写出更优的 SQL 。
使用方法:在查询语句前面加上 explain 运行就可以了。
这也是分析 SQL 时最常用的,也是最推荐的一种分析慢查询的方式。
这篇文章详细讲了执行计划各个字段的含义,大部分是不用记的,只要在使用时,再找到这篇文章对照就行。
1 准备
准备测试数据
为了便于理解,先创建两张测试表,建表及数据写入语句如下:
CREATE DATABASE martin;use martin;drop table if exists t1;CREATE TABLE `t1` (`id` int NOT NULL auto_increment,`a` int DEFAULT NULL,`b` int DEFAULT NULL,`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',PRIMARY KEY (`id`),KEY `idx_a` (`a`),KEY `idx_b` (`b`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;drop procedure if exists insert_t1;delimiter ;;create procedure insert_t1()begindeclare i int;set i=1;while(i<=1000)doinsert into t1(a,b) values(i, i);set i=i+1;end while;end;;delimiter ;call insert_t1();drop table if exists t2;create table t2 like t1;insert into t2 select * from t1;
尝试explain的用法
下面尝试使用 explain 分析一条 SQL,例子如下:
explain select * from t1 where b=100;

2 Explain的结果各字段解释
如下:
加粗的列为需要重点关注的项。
其中 explain 各列都有各种不同的值,这里介绍几个比较重要列常包含的值:
下面将列出它们常见的一些值,可稍微过一遍,不需要完全记下来,在后续分析SQL时,可以返回查询这篇文章的内容并对比各种值的区别。
3 select_type各种值的解释
4 type各种值的解释
上表的这些情况,查询性能从上到下依次是最好到最差。
5 key_len各种字段类型对应的长度
explain 中的 key_len 列用于表示这次查询中,所选择的索引长度有多少字节,常用于判断联合索引有多少列被选择了。下表总结了常用字段类型的 key_len:
6 Extra常见值的解释
7 对比有无索引的执行计划
条件字段是主键的执行计划
explain select * from t1 where id=100;

type是const,表示基于主键或唯一索引的查询
key 是PRIMARY,表示走了主键索引
row 表示扫描的行数,只扫描了一行。
条件字段有索引的执行计划
explain select * from t1 where b=100;

重点关注
type,这里是ref,表示:基于普通索引的等值查询,或者表间等值连接
key这个字段,这里可以看出来,是走了索引的。
然后再看rows,发现只扫描了1行
条件字段没索引的执行计划
删除b字段上的索引
alter table t1 drop index idx_b;
再来看刚才这条语句的执行计划
explain select * from t1 where b=100;

type,是ALL,表示:全表扫描。
key,是NULL,表示没走索引。
rows,这里其实是扫描了很多行,这里是估值,所以不一定准确。
8 获取分区信息
创建测试表并写入数据
CREATE TABLE sales (sale_id INT,sale_date DATE,amount DECIMAL(10, 2)) PARTITION BY RANGE (YEAR(sale_date)) (PARTITION p2022 VALUES LESS THAN (2023),PARTITION p2023 VALUES LESS THAN (2024),PARTITION p2024 VALUES LESS THAN MAXVALUE);INSERT INTO sales (sale_id, sale_date, amount)VALUES(1, '2022-01-01', 100.50),(2, '2022-02-15', 200.75),(3, '2022-03-20', 150.00),(4, '2023-04-10', 300.20),(5, '2023-05-05', 250.80),(6, '2023-06-12', 180.30),(7, '2023-07-08', 220.40),(8, '2024-08-23', 270.60),(9, '2024-09-17', 320.90),(10, '2024-10-05', 280.75);
查询分区表里面的数据
select * from sales where sale_date='2024-09-17';
查看执行计划
explain select * from sales where sale_date='2024-09-17';

explain select * from sales where sale_date>'2023-01-01';

可以看到,在这种情况下,执行计划就可以显示分区信息。
9 获取正在执行语句的执行计划
在一个窗口构造一条慢查询
select *,sleep(100) from t1 limit 1;
在另外一次窗口查看当前连接
show processlist;

再来查询给定连接的执行计划
EXPLAIN FOR CONNECTION 12;

就可以看到,这个连接正在执行SQL的执行计划。
这个通常用来分析正在执行的问题SQL。
10 MySQL 8.0执行计划的新用法
树状执行计划
从MySQL 8.0.16开始,可以输出树状执行计划,并且能返回预估成本和预估的返回行数
explain format=tree select * from t1 where a=100;

cost表示预估成本信息;
rows表示预估扫描行数。
explain analyze
从MySQL 8.0.18开始,引入了EXPLAIN ANALYZE
使用这个,会执行SQL,并返回有关执行成本,返回行数,执行时间,循环次数等信息
explain analyze select * from t1 where a=100;

cost 表示预估的成本信息
rows 前面的表示预估值,后面的表示实际返回的行数
actual time 第一个值是获取第一行的实际时间,第二个值获取所有行的时间,如果循环了多次就是平均时间,单位毫秒
loops 循环次数