MySQL Explain

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: MySQL Explain

概述

本文主要说明MySQL EXPLAIN用途、输出字段说明、Join Type和Extra及实验。
MySQL中explain语句是让你知道MySQL的执行计划,也是为你提供优化器是如何优化你的SQL的。
在实际业务中,我们主要用EXPLAIN来分析SELECT语句为什么会慢,为什么这个SELECT查询没有使用索引,或者SELECT语句使用了查询为什么依然很慢等等。

功能

explain用于想用户提供MySQL如何执行SQL语句。

  • 可以用于分析的语句有SELECT、DELETE、INSERT、UPDATE和REPLACE五种。
  • 优化器提供SQL执行计划信息。
  • 使用连接ID分析该连接的SQL
  • SELECT语句可以使用SHOW WARNINGS可以看到额外的执行计划信息
  • EXPLAIN可以分析分区表

EXPLAIN输出

  1. EXPLAIN输出列
  2. join type说明
  3. Extra信息说明

在我们实际业务开发中,我们主要关注的是join type和Extra信息,也是我们主要优化的两个点。

实验数据

实验基于的MySQL版本:

Server version:        8.0.25 MySQL Community Server - GPL

实验使用的建表及数据:

CREATE TABLE `sys_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '姓名',
  `name_pinyin` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '姓名拼音',
  `id_card` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '身份证号',
  `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '手机号',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_id_card` (`id_card`) USING BTREE COMMENT '唯一索引-身份证号',
  KEY `idx_phone_name` (`phone`,`name`) USING BTREE COMMENT '普通索引-手机号'
) ENGINE=InnoDB AUTO_INCREMENT=3495 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户';

CREATE TABLE `sys_user_info` (
  `id` int NOT NULL AUTO_INCREMENT,
  `age` int NOT NULL COMMENT '年龄',
  `phone` varchar(20) NOT NULL COMMENT '手机号',
  `email` varchar(255) NOT NULL COMMENT '邮箱',
  `birthday` date NOT NULL COMMENT '生日',
  `sys_user_id` int NOT NULL COMMENT '用户ID',
  `self_introduction` varchar(1000) NOT NULL COMMENT '自我介绍',
  `intrest` varchar(255) DEFAULT NULL COMMENT '兴趣',
  `intrest2` varchar(255) DEFAULT NULL COMMENT '兴趣2',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_idx_user_id` (`sys_user_id`) USING BTREE COMMENT '唯一索引-用户ID',
  KEY `idx_phone_email` (`phone`,`email`) USING BTREE COMMENT '普通索引-手机号-邮箱',
  KEY `idx_intrest` (`intrest`) USING BTREE COMMENT '普通索引-兴趣',
  FULLTEXT KEY `idx_fulltext_self_introduction` (`self_introduction`) COMMENT '全文索引-自我介绍'
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户信息';

INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (1, '张三', 'zhangsan', '123456789', '13000000000');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (2, '李四', 'lisi', '200000000', '13000000001');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (3, '王五', 'wangwu', '200000001', '13000000002');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (4, '王五', 'wangwu', '200000003', '13000000002');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (5, '王五', 'wangwu', '200000004', '13000000003');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (6, '王五', 'wangwu', '200000005', '13000000004');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (7, '王五', 'wangwu', '200000006', '13000000005');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (8, '王五', 'wangwu', '200000007', '13000000006');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (9, '王五', 'wangwu', '200000008', '13000000007');
INSERT INTO `employees`.`sys_user`(`id`, `name`, `name_pinyin`, `id_card`, `phone`) VALUES (10, '王五', 'wangwu', '200000009', '13000000008');

INSERT INTO `employees`.`sys_user_info`(`id`, `age`, `phone`, `email`, `birthday`, `sys_user_id`, `self_introduction`, `intrest`, `intrest2`) VALUES (1, 1, '13000000000', 'zhangsan@dev.com', '2022-02-18', 1, 'The CHAR and VARCHAR types are similar, but differ in the way they are stored and retrieved. They also differ in maximum length and in whether trailing spaces are retained.\r\n\r\nThe CHAR and VARCHAR types are declared with a length that indicates the maximum number of characters you want to store. For example, CHAR(30) can hold up to 30 characters.\r\n\r\nThe length of a CHAR column is fixed to the length that you declare when you create the table. The length can be any value from 0 to 255. When CHAR values are stored, they are right-padded with spaces to the specified length. When CHAR values are retrieved, trailing spaces are removed unless the PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.', '123', '456');
INSERT INTO `employees`.`sys_user_info`(`id`, `age`, `phone`, `email`, `birthday`, `sys_user_id`, `self_introduction`, `intrest`, `intrest2`) VALUES (2, 2, '13000000001', 'lisi@dev.com', '2022-02-18', 2, 'Values in VARCHAR columns are variable-length strings. The length can be specified as a value from 0 to 65,535. The effective maximum length of a VARCHAR is subject to the maximum row size (65,535 bytes, which is shared among all columns) and the character set used. See Section 8.4.7, “Limits on Table Column Count and Row Size”.\r\n\r\nIn contrast to CHAR, VARCHAR values are stored as a 1-byte or 2-byte length prefix plus data. The length prefix indicates the number of bytes in the value. A column uses one length byte if values require no more than 255 bytes, two length bytes if values may require more than 255 bytes.', NULL, NULL);

输出字段

输出字段概览

列名 意思
id SELECT标识符
select_type 查询类型
table 输出行的表
partitions 分区信息
type join type
possible_keys 可以选择使用的索引
key 实际使用的索引
ref 与索引比较的列
评估可能要检测的行
filtered 根据表的查询条件需要检测的行与表格总行的百分比
Extra 额外信息,比如:using index, using where, using filesort

字段解释

select type

select type 意思 示例
SIMPLE 就是一个简单的查询,没有使用UNION或者子查询 explain select * from sys_user;
PRIMARY 主查询 explain select * from sys_user where id= (select sys_user_id from sys_user_info limit 1);
UNION UNION语句 explain select from sys_user where id= (select sys_user_id from sys_user_info limit 1) union select from sys_user where id in (select sys_user_id from sys_user_info);
DEPENDENT UNION UNION操作中,查询有内层的SELECT与外层的SELECT 有依赖关系 explain select * from sys_user su where su.id in (select u1.id as uid from sys_user u1 where phone=(select phone from sys_user u2 where u1.id=u2.id) union select t.id as uid from sys_user t where t.id= (select sys_user_id from sys_user_info limit 1) );
UNION RESULT 合并结果 参见UNION
SUBQUERY 子查询 explain select * from sys_user where id= (select sys_user_id from sys_user_info limit 1)
DEPENDENT SUBQUERY 内层子查询与外层子查询有依赖关系 explain select * from sys_user u1 where phone=(select phone from sys_user u2 where u1.id=u2.id);
DERIVED 子查询在FROM子查询 explain select from sys_user su1 join (select from sys_user group by phone) su2 on su1.id=su2.id;
DEPENDENT DERIVED 不好模拟
MATERIALIZED 不好模拟
UNCACHEABLE SUBQUERY 对于外层的主表,子查询不能被缓存,只能每次重新计算 不好模拟
UNCACHEABLE UNION UNION内层不能被缓存,只能每次重新计算 不好模拟

Join Types

MySQL EXPLAIN 的Join Type字段有11种类型,11种类型分别为:

  1. system
  2. const
  3. eq_ref
  4. ref
  5. fulltext
  6. ref_or_null
  7. index_merge
  8. index_subquery
  9. range
  10. index
  11. ALL

以上11种类型从好到坏排序为:system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>index_subquery>range>index>ALL。
这种排序从好到坏排序并不一定代表你的查询速度从好到坏,因为还要看表的数据量。比如:
在one_row_table表中只有一条数据,你执行SQL语句:

select * from one_row_table

这条语句会执行全表扫表,但是他的速度等于system级别,但是explain的输出如下:

MySQL [employees]> explain select * from one_row_table;
+----+-------------+---------------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table         | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+---------------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | one_row_table | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL  |
+----+-------------+---------------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

但是在表数据量大时,这个就会非常慢了。所以要根据表的实际情况来实际分析。下面对各个级别分别说明和实验。

system级别

这是最好的一个级别,也是const的一个特列。这个级别会出现输出中的条件是表中只有一行数据。

const

const级别是只匹配到一行数据。
const级别出现的条件是:

  • 主键
  • 唯一索引

实验

  1. 使用主键查询一条数据
MySQL [employees]> explain select id from one_row_table where id=1;
+----+-------------+---------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table         | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+---------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | one_row_table | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
+----+-------------+---------------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
  1. 使用唯一索引查询一条数据
MySQL [employees]> explain select id from one_row_table where id_card='200000000';
+----+-------------+---------------+------------+-------+-----------------+-----------------+---------+-------+------+----------+-------------+
| id | select_type | table         | partitions | type  | possible_keys   | key             | key_len | ref   | rows | filtered | Extra       |
+----+-------------+---------------+------------+-------+-----------------+-----------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | one_row_table | NULL       | const | uni_idx_id_card | uni_idx_id_card | 1023    | const |    1 |   100.00 | Using index |
+----+-------------+---------------+------------+-------+-----------------+-----------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

eq_ref

连表查询使用了主键或唯一索引,必须使用=号运算符。比较的值可以是字段或者常量。

实验

  1. 连表查询

注意:这里只有sys_user表的join type是eq_ref

MySQL [employees]> EXPLAIN select * from sys_user, sys_user_info where sys_user.id=sys_user_info.sys_user_id;
+----+-------------+---------------+------------+--------+-----------------+---------+---------+-------------------------------------+------+----------+-------+
| id | select_type | table         | partitions | type   | possible_keys   | key     | key_len | ref                                 | rows | filtered | Extra |
+----+-------------+---------------+------------+--------+-----------------+---------+---------+-------------------------------------+------+----------+-------+
|  1 | SIMPLE      | sys_user_info | NULL       | ALL    | uni_idx_user_id | NULL    | NULL    | NULL                                |    1 |   100.00 | NULL  |
|  1 | SIMPLE      | sys_user      | NULL       | eq_ref | PRIMARY         | PRIMARY | 4       | employees.sys_user_info.sys_user_id |    1 |   100.00 | NULL  |
+----+-------------+---------------+------------+--------+-----------------+---------+---------+-------------------------------------+------+----------+-------+
2 rows in set, 1 warning (0.01 sec)

ref

查询使用了普通索引/二级索引,而不是主键索引或唯一索引。使用所以必须满足最左匹配原则。
ref级别规定需要使用=或<=>操作符。

实验

  1. 索引列
MySQL [employees]> EXPLAIN select * from sys_user_info where phone='13000000000';
+----+-------------+---------------+------------+------+---------------------------+-----------------+---------+-------+------+----------+-------+
| id | select_type | table         | partitions | type | possible_keys             | key             | key_len | ref   | rows | filtered | Extra |
+----+-------------+---------------+------------+------+---------------------------+-----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | sys_user_info | NULL       | ref  | idx_phone_email,idx_phone | idx_phone_email | 82      | const |    1 |   100.00 | NULL  |
+----+-------------+---------------+------------+------+---------------------------+-----------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
  1. 连表ref
MySQL [employees]> EXPLAIN select * from sys_user, sys_user_info where sys_user.phone=sys_user_info.phone;
+----+-------------+---------------+------------+------+-----------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table         | partitions | type | possible_keys   | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+---------------+------------+------+-----------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | sys_user_info | NULL       | ALL  | idx_phone_email | NULL | NULL    | NULL |    2 |   100.00 | NULL                                       |
|  1 | SIMPLE      | sys_user      | NULL       | ALL  | idx_phone       | NULL | NULL    | NULL |    3 |    50.00 | Using where; Using join buffer (hash join) |
+----+-------------+---------------+------------+------+-----------------+------+---------+------+------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

fulltext

查询使用了全文索引。

实验

MySQL [employees]> EXPLAIN select * from sys_user_info where match (`self_introduction`) against ('a');
+----+-------------+---------------+------------+----------+--------------------------------+--------------------------------+---------+-------+------+----------+-------------------------------+
| id | select_type | table         | partitions | type     | possible_keys                  | key                            | key_len | ref   | rows | filtered | Extra                         |
+----+-------------+---------------+------------+----------+--------------------------------+--------------------------------+---------+-------+------+----------+-------------------------------+
|  1 | SIMPLE      | sys_user_info | NULL       | fulltext | idx_fulltext_self_introduction | idx_fulltext_self_introduction | 0       | const |    1 |   100.00 | Using where; Ft_hints: sorted |
+----+-------------+---------------+------------+----------+--------------------------------+--------------------------------+---------+-------+------+----------+-------------------------------+
1 row in set, 1 warning (0.00 sec)

eq_or_null

index_merge

unique_subquery

range

范围查询一般是这些操作符:=、 <>、 >、 >=、 <、 <=、 IS NULL、<=>、BETWEEN、LIKE或IN()

实验

MySQL [employees]> explain select * from sys_user where id in  (155,156,157,158,159,162,163,164,166,167,168,169,172,173);
+----+-------------+----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | sys_user | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |   14 |   100.00 | Using where |
+----+-------------+----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

index

index是扫描这个索引,但是要分两种情况:

  • 如果查询能够覆盖索引,那么Extra列会显示Using index提示。覆盖索引就只需要扫描索引就可以完成查询需求。
  • 如果查询不能覆盖索引,那么Extra列不会显示Using index提示。不能覆盖索引就需要使用索引的主键回表查询记录。

实验

  1. 覆盖索引

Extra列说明使用了索引。

MySQL [employees]> explain select phone from sys_user;
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys | key            | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | sys_user | NULL       | index | NULL          | idx_phone_name | 124     | NULL | 2981 |   100.00 | Using index |
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
  1. 非覆盖索引

Extra列未说明使用索引。

MySQL [employees]> explain select phone from sys_user_info order by id;
+----+-------------+---------------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table         | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+---------------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | sys_user_info | NULL       | index | NULL          | PRIMARY | 4       | NULL |    2 |   100.00 | NULL  |
+----+-------------+---------------+------------+-------+---------------+---------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

ALL

ALL级别是全表扫描。

实验

MySQL [employees]> explain select * from sys_user;
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | sys_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2482 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

Extra

Extra字段主要关注Using filesort或Using temporary输出,如果看到有这两个输出,那么这个查询应该被优化。

  • Backward index scan: InnoDB表中优化器使用降序索引,这个会和Using index一起输出。
  • Child of 'table' pushed join@1
  • const row not found: 根据查询条件没有找到行。
  • Deleting all rows: 删除所有的行。
  • Distinct:
  • FirstMatch
  • Full scan on NULL key
  • Impossible HAVING: HAVING的条件不可能出现。
  • Impossible WHERE: WHERE子句的条件可能出现。比如:在一个不能为空的字段搜索空: explain select * from sys_user where id is NULL;
  • No tables used: 没有FROM 子句或FORM DUAL
  • Not exists
  • Plan isn't ready yet
  • Using filesort:
  • Using index
  • Using index condition
  • Using temporary
  • Using where

参考

  1. https://dev.mysql.com/doc/refman/8.0/en/using-explain.html
  2. https://blog.csdn.net/bigtree_3721/article/details/51338104
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
SQL 存储 关系型数据库
SQL优化之Explain详解(mysql)
`Explain`是MySQL中用于分析SQL查询执行计划的工具。它可以帮助我们了解MySQL如何执行SQL语句,包括如何使用索引、预计的行数以及查询的顺序。以下是`Explain`输出的关键列及其含义的简要摘要: 1. **id**:查询的序列号,表示查询中的子句层次,id越大优先级越高。 2. **select_type**:表示查询的类型,如SIMPLE(简单查询)、PRIMARY(主查询,多表查询中的第一个查询)、SUBQUERY(子查询)、DERIVED(派生表)或UNION(UNION操作的查询部分)。 3. **table**:查询涉及的表名,如果是子查询,可能显示为衍生表
85 0
|
1月前
|
SQL 关系型数据库 MySQL
MySQL EXPLAIN该如何分析?
本文将详细介绍MySQL中`EXPLAIN`关键字的工作原理及结果字段解析,帮助优化查询性能。`EXPLAIN`可显示查询SQL的执行计划,其结果包括`id`、`select_type`、`table`等字段。通过具体示例和优化建议,帮助你理解和应用`EXPLAIN`,提升数据库查询效率。
78 0
|
4月前
|
SQL 关系型数据库 MySQL
mysql性能调优:EXPLAIN命令21
【7月更文挑战第21天】掌握SQL性能调优:深入解析EXPLAIN命令的神奇用法!
59 1
|
4月前
|
SQL 缓存 关系型数据库
MySQL|浅谈explain的使用
【7月更文挑战第11天】
|
4月前
|
SQL 存储 数据库
MySQL设计规约问题之性能分析工具如Sql explain、show profile和mysqlsla在数据库性能优化中有什么作用
MySQL设计规约问题之性能分析工具如Sql explain、show profile和mysqlsla在数据库性能优化中有什么作用
|
4月前
|
SQL 索引 关系型数据库
MySQL设计规约问题之为什么推荐使用EXPLAIN来检查SQL查询
MySQL设计规约问题之为什么推荐使用EXPLAIN来检查SQL查询
|
5月前
|
SQL 关系型数据库 MySQL
MySQL数据库——索引(4)-SQL性能分析-profile详情、explain(profile查看指令,explain执行计划中各个字段的含义)
MySQL数据库——索引(4)-SQL性能分析-profile详情、explain(profile查看指令,explain执行计划中各个字段的含义)
66 2
|
5月前
|
SQL 存储 关系型数据库
MySQL的查询计划(EXPLAIN)
MySQL的查询计划(EXPLAIN)
54 2
|
6月前
|
存储 SQL 关系型数据库
MySQL 底层数据结构 聚簇索引以及二级索引 Explain的使用
MySQL 底层数据结构 聚簇索引以及二级索引 Explain的使用
68 0