一文带你快速看懂 MySQL 执行计划
通常查询慢查询SQL
语句时会使用EXPLAIN
命令来查看SQL
语句的执行计划,通过返回的信息,可以了解到Mysql
优化器是如何执行SQL
语句,通过分析可以帮助我们提供优化的思路。
1. Explain 作用
explain 命令主要用于查看 SQL 语句的执行计划,该命令可以模拟优化器执行 SQL 查询语句,可以帮助我们编写和优化 SQL。那么 explain 具体可以提供哪些信息,帮助我们如何去优化 SQl 的呢?
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
2. Explain 如何使用
使用方式: explain + 待执行的sql
explain 会返回一个待执行 SQL 的执行计划列表,列表包含了 12 个字段,字段共同描述了 SQL 在执行计划中将会采取何种方式执行。以下列表详细描述了执行计划表的字段含义:
字段名称 | 描述 |
---|---|
id | 执行 select 语句查询的序列号,决定表的读取顺序 |
select_type | 查询的类型,也就是数据读取操作的操作类型 |
table | 查询的表名 |
partitions | 表分区 |
type | 访问类型 |
possible_keys | 可使用的索引。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用到。如果这个字段为 null 但是字段 key 不为 null,这种情况就是在查找时没有可以使用的二级索引树,但是二级索引中包含了需要查询的字段,于是就不再查找聚簇索引(聚簇索引比较大),转而扫描这个二级索引树(二级索引树比较小),并且此时一般访问类型 type 为 index,及扫描整棵索引树。 |
key | 实际扫描使用的索引。如果为 null,则没有使用索引;查询中若使用了覆盖索引,则该索引仅出现在key列表中; |
key_len | 索引中使用的字节数。可通过该列计算查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好;key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的; |
ref | 显示索引的哪一列被使用了。如果可能的话,是一个常数,哪些列或常量别用于查找索引列上的值; |
rows | 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数; |
filtered | 搜索条件过滤后剩余数据的百分比。 |
Extra | 包含不适合在其它列中显示但十分重要的额外信息 |
3. 关键字段分析
(1)id
执行 select 语句查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序,它有三种情况:
类型名称 | 描述 |
---|---|
id相同 | 执行顺序由上至下 |
id不同 | 如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行 |
id相同不同,同时存在 | 如果 id 相同,可以认为是一组,从上往下顺序执行,在所有组中,id值越大,优先级越高,越先执行 |
(2)select_type
就是数据读取操作的操作类型,他一共有以下几种:
类型名称 | 描述 |
---|---|
simple | 简单的 select 查询,查询中不包含子查询或者 union; |
primary | 查询中若包含任何复杂的子查询,最外层查询则被标记; |
subquery | 在 select 或者 where 列表中包含了子查询; |
dependent subquery | 子查询中的第一个 SELECT, 取决于外面的查询。 即子查询依赖于外层查询的结果。 |
derived | 在 from 列表中包含的子查询被标记为 DERIVED(衍生表),mysql 会递归执行这些子查询,把结果放临时表中; |
union | 若第二个 select 出现在 union 之后,则被标记为 union,若 union 包含在 from 子句的子查询中,外层 select 将被标记为 DERIVED; |
union result | 从 union 表(即 union 合并的结果集)中获取 select 查询的结果; |
meterialized | 物化表,子查询关联查询时,子查询结果存储在物化临时表,然后根据临时表中的数据去主表匹配。 |
dependent union | UNION 中的第二个或后面的查询语句,取决于外面的查询 |
(3)table
显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为 null,也可以是以下之一:
类型名称 | 描述 |
---|---|
表示这个是临时表,后边的N就是执行计划中的 id,表示结果来自于这个查询产生。 | |
N > | 与 |
该行是指与物化子查询该行的结果 id 的值 N。 |
(4)partitions
查询将匹配记录的分区。该值NULL
用于非分区表。
(5)type
依次从好到差:
system
>const
>eq_ref
>ref
>ref_or_null
>range
>index
>ALL
除了all
之外,其他的type
都可以使用到索引,除了index_merge
之外,其他的type
只可以用到一个索引。
我们自己创建一系列表来实验下:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for goods -- ---------------------------- DROP TABLE IF EXISTS `goods`; CREATE TABLE `goods` ( `id` int(11) NOT NULL, `sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of goods -- ---------------------------- INSERT INTO `goods` VALUES (1, 'sn123456', '衣服'); -- ---------------------------- -- Table structure for sku -- ---------------------------- DROP TABLE IF EXISTS `sku`; CREATE TABLE `sku` ( `id` int(11) NOT NULL, `goods_id` int(11) NOT NULL, `status` int(11) NOT NULL, `deleted` int(11) NOT NULL, `barcode` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `index_2`(`name`) USING BTREE, INDEX `index_1`(`goods_id`, `status`, `deleted`, `barcode`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sku -- ---------------------------- INSERT INTO `sku` VALUES (1, 1, 1, 0, 'kt123456', '黑色'); SET FOREIGN_KEY_CHECKS = 1;
system
表只有一行记录(等于系统表),这是 const 类型的特例,平时不会出现,这个也可忽略不计;
const
表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行记录,所以很快。 如果将主键置于 where 列表中,mysql 就能将该查询转换成一个常量;
EXPLAIN SELECT * FROM sku WHERE id=1;复制代码
eq_ref
唯一性索引扫描,对于每一个索引键,表中只有一条记录与之匹配,常用于主键或唯一索引扫描;此类型通常出现在多表的 join 等值查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果,查询效率较高。
EXPLAIN SELECT * FROM sku,goods WHERE sku.goods_id=goods.id;
ref
非唯一性索引扫描,返回匹配某个单独值得所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体;
EXPLAIN SELECT * FROM sku WHERE goods_id=1;
ref_or_null
二级索引等值比较同时限定 is null 。
EXPLAIN SELECT * FROM sku WHERE name='123456' or name IS NULL;
range
只检索给定范围的行,使用一个索引来选择行,key列显示使用哪个索引,一般就是在你的 where 语句中出现了 between、<、>、in 等的查询;这种范围索引扫描比全表扫描要好,因为它只需要开始于索引的某一个点,结束于另一个点,不用扫描全部索引;
EXPLAIN SELECT * FROM sku WHERE id BETWEEN 1 and 10;
index
index 和 all 区别为 index 类型只遍历索引树,这通常比 all 快,因为索引文件通常比数据文件小;也就是说虽然 all 和 index 都是读写表,但 index 是从索引中读取的,而 all 是从硬盘中读的;
EXPLAIN SELECT barcode FROM sku WHERE deleted=0;
all
也就是全表扫描;
EXPLAIN SELECT * FROM sku WHERE deleted=0;
(6)possible_keys
查询可能使用到的索引都会在这里列出来。
(7)key
查询真正使用到的索引,select_type
为index_merge
时,这里可能出现两个以上的索引,其他的select_type
这里只会出现一个。
(8)key_len
key_len 表示该列计算查询中使用的索引的长度。例如:SELECT * FROM table where age = 1 and name like 'xx'
,假设 age 是 int 类型且不可为 null;name 是 varchar(20) 类型且可以为 null,编码为 utf8。若以这两个字段为索引查询,那么 key_len 的值为 4 + 3 * 20 + 2 + 1 = 67
。具体计算规则如下表所示:
值类型 | 值名称 | 描述 |
---|---|---|
字符串 | CHAR(n) | n 字节长度 |
VARCHAR(n) | 如果是 utf8 编码,则是 3 n + 2字节;;如果是 utf8mb4 编码,则是 4 n + 2 字节。 | |
数值类型 | TINYINT | 1字节 |
SMALLINT | 2字节 | |
MEDIUMINT | 3字节 | |
INT | 4字节 | |
BIGINT | 8字节 | |
时间类型 | DATE | 3字节 |
TIMESTAMP | 4字节 | |
DATETIME | 8字节 | |
字段属性 | NULL 属性 占用一个字节。如果一个字段是 NOT NULL 的, 则不占用。 |
(9)ref
如果是使用的常数等值查询,这里会显示const
,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func
。
(10)rows
这里是执行计划中估算的扫描行数,不是精确值。
(11)filtered
使用explain extended
时会出现这个列,5.7
之后的版本默认就有这个字段,不需要使用explain extended
了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。
(12)Extra
这个列可以显示的信息非常多,有几十种,常用的有:
1、distinct:在select
部分使用了distinct
关键字
2、no tables used:不带from
字句的查询或者From dual
查询。使用not in()
形式子查询或not exists()
运算符的连接查询,这种叫做反连接。即,一般连接查询是先查询内表,再查询外表,反连接就是先查询外表,再查询内表。
3、using filesort:说明mysql
会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。mysql
中无法利用索引完成的排序操作称为“文件排序”。排序时无法使用到索引时,就会出现这个。常见于order by
语句中,需要尽快优化
4、using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。
5、using join buffer(block nested loop),using join buffer(batched key accss) :5.6.x
之后的版本优化关联查询的BNL
,BKA
特性。主要是减少内表的循环数量以及比较顺序地扫描查询。
6、using sort_union,using_union,using intersect,using sort_intersection:
- using intersect:表示使用
and
的各个索引的条件时,该信息表示是从处理结果获取交集 - using union:表示使用
or
连接各个使用索引的条件时,该信息表示从处理结果获取并集 - using sort_union和using sort_intersection:与前面两个对应的类似,只是他们是出现在用
and
和or
查询信息量大时,先查询主键,然后进行排序合并后,才能读取记录并返回。
7、using temporary:表示使用了临时表存储中间结果。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status
变量,used_tmp_table
,used_tmp_disk_table
才能看出来。常见于order by
和分组查询group by
。group by
一定要遵循所建索引的顺序与个数。需要尽快优化
8、using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server
层进行过滤。查询条件中分为限制条件和检查条件,5.6
之前,存储引擎只能根据限制条件扫描数据并返回,然后server
层根据检查条件进行过滤再返回真正符合查询的数据。5.6.x
之后支持ICP
特性(index condition pushdown,索引下推),可以把检查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就大大减少了存储引擎扫描的记录数量。extra
列显示using index condition
9、firstmatch(tb_name) :5.6.x
开始引入的优化子查询的新特性之一,常见于where
字句含有in()
类型的子查询。如果内表的数据量比较大,就可能出现这个
10、loosescan(m..n) :5.6.x
之后引入的优化子查询的新特性之一,在in()
类型的子查询中,子查询返回的可能有重复记录时,就可能出现这个
4. Explain 主要关注点
总的来说,我们只需要关注结果中的几列:
列名 | 备注 |
---|---|
type | 本次查询表联接类型,从这里可以看到本次查询大概的效率 |
key | 最终选择的索引,如果没有索引的话,本次查询效率通常很差 |
key_len | 本次查询用于结果过滤的索引实际长度 |
rows | 预计需要扫描的记录数,预计需要扫描的记录数越小越好 |
Extra | 额外附加信息,主要确认是否出现Using filesort 、Using temporary 这两种情况 |
再来看下Extra
列中需要注意出现的几种情况:
关键字 | 备注 |
---|---|
Using filesort | 将用外部排序而不是按照索引顺序排列结果,数据较少时从内存排序,否则需要在磁盘完成排序,代价非常高,需要添加合适的索引 |
Using temporary | 需要创建一个临时表来存储结果,这通常发生在对没有索引的列进行GROUP BY 时,或者ORDER BY 里的列不都在索引里,需要添加合适的索引 |
Using index | 表示MySQL 使用覆盖索引避免全表扫描,不需要再到表中进行二次查找数据,这是比较好的结果之一。注意不要和type 中的index 类型混淆 |
Using where | 通常是进行了全表/全索引扫描后再用WHERE 子句完成结果过滤,需要添加合适的索引 |
Impossible WHERE | 对Where 子句判断的结果总是false而不能选择任何数据,例如where 1=0 ,无需过多关注 |
Select tables optimized away | 使用某些聚合函数来访问存在索引的某个字段时,优化器会通过索引直接一次定位到所需要的数据行完成整个查询,例如MIN()\MAX() ,这种也是比较好的结果之一 |
【相关推荐:mysql视频教程】
以上就是一文带你快速看懂 MySQL 执行计划的详细内容,更多请关注其它相关文章!