一文学会MySQL的explain工具
开篇说明
(1) 本文将细致介绍MySQL的explain工具,是下一篇《一文读懂MySQL的索引机制及查询优化》的准备篇。
(2) 本文主要基于MySQL5.7
版本(https://dev.mysql.com/doc/refman/5.7/en/
),MySQL8.x
版本可另行翻阅对应版本文档(https://dev.mysql.com/doc/refman/8.0/en/
)。
(3) 演示过程中的建库、建表、建索引等语句仅为了测试explain工具的使用,并未考虑实际应用场景的合理性。
explain工具介绍
相关文档:
https://dev.mysql.com/doc/refman/5.7/en/explain.html
https://dev.mysql.com/doc/refman/5.7/en/using-explain.html
EXPLAIN is used to obtain a query execution plan (that is, an explanation of how MySQL would execute a query).
简单翻译一下,就是explain用于获取查询执行计划(即MySQL是如何执行一个查询的)。
工作中,我们会遇到慢查询,这个时候我们就可以在select
语句之前增加explain
关键字,模拟MySQL优化器执行SQL语句,从而分析该SQL语句有没有用上索引、是否全表扫描、能否进一步优化等。
还是来个快速入门的案例比较直观,依次在mysql的命令行执行下面几条语句(建库、建表sql脚本见下面的数据准备
部分):
mysql> use `explain_test`;
mysql> select * from tb_hero where hero_name = '李寻欢' and book_id = 1;
mysql> explain select * from tb_hero where hero_name = '李寻欢' and book_id = 1;
mysql> show warnings \G
得到下面的输出:
mysql> use `explain_test`;
Database changed
mysql> select * from tb_hero where hero_name = '李寻欢' and book_id = 1;
+---------+-----------+--------------+---------+
| hero_id | hero_name | skill | book_id |
+---------+-----------+--------------+---------+
| 1 | 李寻欢 | 小李飞刀 | 1 |
+---------+-----------+--------------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tb_hero where hero_name = '李寻欢' and book_id = 1;
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | tb_hero | NULL | ref | idx_book_id_hero_name | idx_book_id_hero_name | 136 | const,const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings \G
*************************** 1. row ***************************
Level: Note
Code: 1003
Message: /* select#1 */ select `explain_test`.`tb_hero`.`hero_id` AS `hero_id`,`explain_test`.`tb_hero`.`hero_name` AS `hero_name`,`explain_test`.`tb_hero`.`skill` AS `skill`,`explain_test`.`tb_hero`.`book_id` AS `book_id` from `explain_test`.`tb_hero` where ((`explain_test`.`tb_hero`.`book_id` = 1) and (`explain_test`.`tb_hero`.`hero_name` = '李寻欢'))
1 row in set (0.00 sec)
先别急explain
语句输出结果每一列表示什么意思(后面会具体描述),用show warnings
命令可以得到优化后的查询语句大致长什么样子。
补充:
- 有关
show warnings
更详细的使用见https://dev.mysql.com/doc/refman/5.7/en/show-warnings.html
- 有关获取
explain
额外的输出信息见https://dev.mysql.com/doc/refman/5.7/en/explain-extended.html
原SQL语句:
select * from tb_hero where hero_name = '李寻欢' and book_id = 1;
优化后的SQL语句:
select `explain_test`.`tb_hero`.`hero_id` AS `hero_id`,
`explain_test`.`tb_hero`.`hero_name` AS `hero_name`,
`explain_test`.`tb_hero`.`skill` AS `skill`,
`explain_test`.`tb_hero`.`book_id` AS `book_id`
from `explain_test`.`tb_hero`
where ((`explain_test`.`tb_hero`.`book_id` = 1) and (`explain_test`.`tb_hero`.`hero_name` = '李寻欢'))
可以看出,MySQL优化器把*
优化成具体的列名,另外把我where
中的两个过滤条件hero_name
、book_id
先后顺序调换了一下,这种顺序调换是概率性事件还是另有文章?
(哈哈哈,(●´ω`●)留个悬念,本篇仅介绍explain工具,读了下篇《一文读懂MySQL的索引机制及查询优化》后自然豁然开朗)
数据准备
为了方便演示explain工具的使用以及输出结果的含义,准备了一些测试数据,初始化sql脚本如下:
-- ----------------------------
-- create database
-- ----------------------------
DROP database IF EXISTS `explain_test`;
create database `explain_test` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- switch database
use `explain_test`;
-- ----------------------------
-- table structure for `tb_book`
-- ----------------------------
DROP TABLE IF EXISTS `tb_book`;
CREATE TABLE `tb_book` (
`book_id` int(11) NOT NULL,
`book_name` varchar(64) DEFAULT NULL,
`author` varchar(32) DEFAULT NULL,
PRIMARY KEY (`book_id`),
UNIQUE KEY `uk_book_name` (`book_name`) USING BTREE,
INDEX `idx_author` (`author`) USING BTREE
);
BEGIN;
INSERT INTO `tb_book`(`book_id`, `book_name`, `author`) VALUES (1, '多情剑客无情剑', '古龙');
INSERT INTO `tb_book`(`book_id`, `book_name`, `author`) VALUES (2, '笑傲江湖', '金庸');
INSERT INTO `tb_book`(`book_id`, `book_name`, `author`) VALUES (3, '倚天屠龙记', '金庸');
INSERT INTO `tb_book`(`book_id`, `book_name`, `author`) VALUES (4, '射雕英雄传', '金庸');
INSERT INTO `tb_book`(`book_id`, `book_name`, `author`) VALUES (5, '绝代双骄', '古龙');
COMMIT;
-- ----------------------------
-- table structure for `tb_hero`
-- ----------------------------
DROP TABLE IF EXISTS `tb_hero`;
CREATE TABLE `tb_hero` (
`hero_id` int(11) NOT NULL,
`hero_name` varchar(32) DEFAULT NULL,
`skill` varchar(64) DEFAULT NULL,
`book_id` int(11) DEFAULT NULL,
PRIMARY KEY (`hero_id`),
INDEX `idx_book_id_hero_name`(`book_id`, `hero_name`) USING BTREE
);
BEGIN;
INSERT INTO `tb_hero`(`hero_id`, `hero_name`, `skill`, `book_id`) VALUES (1, '李寻欢', '小李飞刀', 1);
INSERT INTO `tb_hero`(`hero_id`, `hero_name`, `skill`, `book_id`) VALUES (2, '令狐冲', '独孤九剑', 2);
INSERT INTO `tb_hero`(`hero_id`, `hero_name`, `skill`, `book_id`) VALUES (3, '张无忌', '九阳神功', 3);
INSERT INTO `tb_hero`(`hero_id`, `hero_name`, `skill`, `book_id`) VALUES (4, '郭靖', '降龙十八掌', 4);
INSERT INTO `tb_hero`(`hero_id`, `hero_name`, `skill`, `book_id`) VALUES (5, '花无缺', '移花接玉', 5);
INSERT INTO `tb_hero`(`hero_id`, `hero_name`, `skill`, `book_id`) VALUES (6, '任我行', '吸星大法', 2);
COMMIT;
-- ----------------------------
-- Table structure for `tb_book_hero`
-- ----------------------------
DROP TABLE IF EXISTS `tb_book_hero`;
CREATE TABLE `tb_book_hero` (
`book_id` int(11) NOT NULL,
`hero_id` int(11) NOT NULL,
`user_comment` varchar(255) DEFAULT NULL,
PRIMARY KEY (`book_id`, `hero_id`) USING BTREE
) ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
BEGIN;
INSERT INTO `tb_book_hero`(`book_id`, `hero_id`, `user_comment`) VALUES (1, 1, '小李飞刀,例无虚发,夺魂索命,弹指之间');
INSERT INTO `tb_book_hero`(`book_id`, `hero_id`, `user_comment`) VALUES (2, 2, '令狐少侠留步!');
INSERT INTO `tb_book_hero`(`book_id`, `hero_id`, `user_comment`) VALUES (3, 3, '尝遍世间善恶,归来仍是少年');
INSERT INTO `tb_book_hero`(`book_id`, `hero_id`, `user_comment`) VALUES (4, 4, '我只要我的靖哥哥!');
INSERT INTO `tb_book_hero`(`book_id`, `hero_id`, `user_comment`) VALUES (5, 5, '风采儒雅亦坦荡,武艺精深兼明智。');
INSERT INTO `tb_book_hero`(`book_id`, `hero_id`, `user_comment`) VALUES (2, 6, '有人就有恩怨,有恩怨就有江湖,人心即是江湖,你如何退出!');
COMMIT;
explain的输出结果
相关文档:
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
看一下官方文档显示的关于explain输出结果列(explain output columns
)的含义:
Column | JSON Name | Meaning |
---|---|---|
id | select_id | The SELECT identifier |
select_type | None | The SELECT type |
table | table_name | The table for the output row |
partitions | partitions | The matching partitions |
type | access_type | The join type |
possible_keys | possible_keys | The possible indexes to choose |
key | key | The index actually chosen |
key_len | key_length | The length of the chosen key |
ref | ref | The columns compared to the index |
rows | rows | Estimate of rows to be examined |
filtered | filtered | Percentage of rows filtered by table condition |
Extra | None | Additional information |
其中JSON Name
指的是当设定FORMAT=JSON
时,列名在json中显示的name,见下面的演示就明白了
mysql> explain select * from tb_book \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tb_book
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 5
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
mysql> explain FORMAT=JSON select * from tb_book \G
*************************** 1. row ***************************
EXPLAIN: {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "2.00"
},
"table": {
"table_name": "tb_book",
"access_type": "ALL",
"rows_examined_per_scan": 5,
"rows_produced_per_join": 5,
"filtered": "100.00",
"cost_info": {
"read_cost": "1.00",
"eval_cost": "1.00",
"prefix_cost": "2.00",
"data_read_per_join": "1K"
},
"used_columns": [
"book_id",
"book_name",
"author"
]
}
}
}
1 row in set, 1 warning (0.00 sec)
下面重点看一下比较重要的几个字段。
id列
id
是select
的唯一标识,有几个select
就有几个id,并且id的顺序是按select
出现的顺序增长的,id值越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。
为了验证上面的结论,临时关闭mysql5.7
对子查询(sub queries
)产生的衍生表(derived tables
)的合并优化
set session optimizer_switch='derived_merge=off';
详情见:
https://dev.mysql.com/doc/refman/5.7/en/switchable-optimizations.html
https://dev.mysql.com/doc/refman/5.7/en/derived-table-optimization.html
mysql> set session optimizer_switch='derived_merge=off';
Query OK, 0 rows affected (0.00 sec)
mysql> select (select count(1) from tb_book) as book_count, (select count(1) from tb_hero) as hero_count from (select * from tb_book_hero) as book_hero;
+------------+------------+
| book_count | hero_count |
+------------+------------+
| 5 | 6 |
| 5 | 6 |
| 5 | 6 |
| 5 | 6 |
| 5 | 6 |
| 5 | 6 |
+------------+------------+
6 rows in set (0.00 sec)
mysql> explain select (select count(1) from tb_book) as book_count, (select count(1) from tb_hero) as hero_count from (select * from tb_book_hero) as book_hero;
+----+-------------+--------------+------------+-------+---------------+-----------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+-------+---------------+-----------------------+---------+------+------+----------+-------------+
| 1 | PRIMARY | <derived4> | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | NULL |
| 4 | DERIVED | tb_book_hero | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | NULL |
| 3 | SUBQUERY | tb_hero | NULL | index | NULL | idx_book_id_hero_name | 136 | NULL | 6 | 100.00 | Using index |
| 2 | SUBQUERY | tb_book | NULL | index | NULL | uk_book_name | 259 | NULL | 5 | 100.00 | Using index |
+----+-------------+--------------+------------+-------+---------------+-----------------------+---------+------+------+----------+-------------+
4 rows in set, 1 warning (0.00 sec)
mysql> set session optimizer_switch='derived_merge=on';
Query OK, 0 rows affected (0.00 sec)
可见,查询语句中有4个select,先执行的是select * from tb_book_hero
,然后执行select count(1) from tb_hero
,再执行select count(1) from tb_book
,最后执行select book_count, hero_count from book_hero
select_type列
select_type
表示的是查询类型,常见的包括SIMPLE
、PRIMARY
、SUBQUERY
、DERIVED
、UNION
(1) SIMPLE:简单查询(不包含子查询和UNION查询)
mysql> explain select * from tb_book where book_id = 1;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_book | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
(2) PRIMARY:复杂查询中最外层的查询
(3) SUBQUERY:包含在select中的子查询(不在from子句中)
(4) DERIVED:包含在from子句中的子查询,MySQL会将结果存放在一个临时表中,也称为派生表(derived tables
)
这3种select_type见下面的例子
mysql> set session optimizer_switch='derived_merge=off';
Query OK, 0 rows affected (0.00 sec)
mysql> explain select (select count(1) from tb_book) as book_count from (select * from tb_book_hero) as book_hero;
+----+-------------+--------------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| 1 | PRIMARY | <derived3> | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | NULL |
| 3 | DERIVED | tb_book_hero | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | NULL |
| 2 | SUBQUERY | tb_book | NULL | index | NULL | uk_book_name | 259 | NULL | 5 | 100.00 | Using index |
+----+-------------+--------------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
mysql> set session optimizer_switch='derived_merge=on';
Query OK, 0 rows affected (0.00 sec)
(5) UNION:在UNION中的第二个和随后的select
mysql> select * from tb_book where book_id = 1 union all select * from tb_book where book_name = '笑傲江湖';
+---------+-----------------------+--------+
| book_id | book_name | author |
+---------+-----------------------+--------+
| 1 | 多情剑客无情剑 | 古龙 |
| 2 | 笑傲江湖 | 金庸 |
+---------+-----------------------+--------+
2 rows in set (0.00 sec)
mysql> explain select * from tb_book where book_id = 1 union all select * from tb_book where book_name = '笑傲江湖';
+----+-------------+---------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
| 1 | PRIMARY | tb_book | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | tb_book | NULL | const | uk_book_name | uk_book_name | 259 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
table列
table
表示查询涉及的表或衍生表。
常见table列是<derivenN>
格式,表示当前查询依赖id=N
的查询,需先执行id=N
的查询。上面含select_type
为DERIVED
的查询就是这种情况,这里不再重复举例。
type列
相关文档:
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#explain-join-types
type列是判断查询是否高效的重要依据,我们可以通过type字段的值,判断此次查询是全表扫描
还是索引扫描
等,进而进一步优化查询。
一般来说表示查询性能最优到最差依次为:NULL > system > const > eq_ref > ref > range > index > ALL
前面的几种类型都是利用到了索引来查询数据, 因此可以过滤部分或大部分数据, 查询效率自然就比较高了。
而后面的index
类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比ALL
类型稍快。
所以,应当尽可能地保证查询达到range
级别,最好达到ref
。
(0) NULL: 不用访问表或者索引,直接就能得到结果,如:在索引列中选取最大值,执行时不需要再访问表
mysql> explain select max(book_id) from tb_book;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| 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 | Select tables optimized away |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
1 row in set, 1 warning (0.00 sec)
(1) system:The table has only one row. This is a special case of the const
join type.
当查询的表只有一行的情况下,system
是const
类型的特例,
(2) const:It is used when you compare all parts of a PRIMARY KEY
or UNIQUE index
to constant values
.
针对主键
或唯一索引
的等值查询扫描, 最多只返回一行数据。const
查询速度非常快, 因为它仅仅读取一次即可。
关于type列为system
、const
的情况,见下面的示例:
mysql> set session optimizer_switch='derived_merge=off';
Query OK, 0 rows affected (0.00 sec)
mysql> explain select * from (select * from tb_book where book_id = 5) as book;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
| 1 | PRIMARY | <derived2> | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 2 | DERIVED | tb_book | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
mysql> set session optimizer_switch='derived_merge=on';
Query OK, 0 rows affected (0.00 sec)
(3) eq_ref:It is used when all parts of an index are used by the join and the index is a PRIMARY KEY
or UNIQUE NOT NULL index
.
此类型通常出现在多表的join查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果,并且查询的比较操作通常是=
,查询效率较高。
mysql> select tb_hero.*, tb_book_hero.user_comment from tb_book_hero, tb_hero where tb_book_hero.book_id = 2 and tb_book_hero.hero_id = tb_hero.hero_id;
+---------+-----------+--------------+---------+--------------------------------------------------------------------------------------+
| hero_id | hero_name | skill | book_id | user_comment |
+---------+-----------+--------------+---------+--------------------------------------------------------------------------------------+
| 2 | 令狐冲 | 独孤九剑 | 2 | 令狐少侠留步! |
| 6 | 任我行 | 吸星大法 | 2 | 有人就有恩怨,有恩怨就有江湖,人心即是江湖,你如何退出! |
+---------+-----------+--------------+---------+--------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
mysql> explain select tb_hero.*, tb_book_hero.user_comment from tb_book_hero, tb_hero where tb_book_hero.book_id = 2 and tb_book_hero.hero_id = tb_hero.hero_id;
+----+-------------+--------------+------------+--------+---------------+---------+---------+-----------------------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+--------+---------------+---------+---------+-----------------------------------+------+----------+-------+
| 1 | SIMPLE | tb_book_hero | NULL | ref | PRIMARY | PRIMARY | 4 | const | 2 | 100.00 | NULL |
| 1 | SIMPLE | tb_hero | NULL | eq_ref | PRIMARY | PRIMARY | 4 | explain_test.tb_book_hero.hero_id | 1 | 100.00 | NULL |
+----+-------------+--------------+------------+--------+---------------+---------+---------+-----------------------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
mysql> select tb_hero.*, tb_book_hero.user_comment from tb_book_hero join tb_hero on tb_book_hero.book_id = 2 and tb_book_hero.hero_id = tb_hero.hero_id;
+---------+-----------+--------------+---------+--------------------------------------------------------------------------------------+
| hero_id | hero_name | skill | book_id | user_comment |
+---------+-----------+--------------+---------+--------------------------------------------------------------------------------------+
| 2 | 令狐冲 | 独孤九剑 | 2 | 令狐少侠留步! |
| 6 | 任我行 | 吸星大法 | 2 | 有人就有恩怨,有恩怨就有江湖,人心即是江湖,你如何退出! |
+---------+-----------+--------------+---------+--------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
mysql> explain select tb_hero.*, tb_book_hero.user_comment from tb_book_hero join tb_hero on tb_book_hero.book_id = 2 and tb_book_hero.hero_id = tb_hero.hero_id;
+----+-------------+--------------+------------+--------+---------------+---------+---------+-----------------------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+--------+---------------+---------+---------+-----------------------------------+------+----------+-------+
| 1 | SIMPLE | tb_book_hero | NULL | ref | PRIMARY | PRIMARY | 4 | const | 2 | 100.00 | NULL |
| 1 | SIMPLE | tb_hero | NULL | eq_ref | PRIMARY | PRIMARY | 4 | explain_test.tb_book_hero.hero_id | 1 | 100.00 | NULL |
+----+-------------+--------------+------------+--------+---------------+---------+---------+-----------------------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
(4) ref: It is used if the join uses only a leftmost prefix of the key or if the key is not a PRIMARY KEY or UNIQUE index (in other words, if the join cannot select a single row based on the key value).
相比eq_ref
,不使用唯一索引,而是使用普通索引或者唯一性索引的最左前缀,可能会找到多个符合条件的行。
- 简单的
select
查询,author
列上建有普通索引(非唯一索引)
mysql> explain select * from tb_book where author = '古龙';
+----+-------------+---------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_book | NULL | ref | idx_author | idx_author | 131 | const | 2 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
- 关联表查询,
tb_book_hero
表使用了联合主键PRIMARY KEY (book_id, hero_id)
,这里使用到了左边前缀book_id
进行过滤。
mysql> explain select * from tb_book_hero where book_id = 3;
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_book_hero | NULL | ref | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
(5) range: It can be used when a key column is compared to a constant using any of the =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, or IN() operators
扫描部分索引(范围扫描),对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>、in等查询
mysql> explain select * from tb_book where book_id > 3;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_book | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
(6) index:the index tree is scanned, MySQL can use this type when the query uses only columns that are part of a single index.
表示全索引扫描(full index scan), 和ALL类型类似, 只不过ALL类型是全表扫描, 而index类型则仅仅扫描所有的索引, 而不扫描数据.
mysql> explain select book_name from tb_book;
+----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_book | NULL | index | NULL | uk_book_name | 259 | NULL | 5 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
上面的例子中, 我们查询的book_name
字段上恰好有索引, 因此我们直接从索引中获取数据就可以满足查询的需求了, 而不需要查询表中的数据。因此这样的情况下, type的值是index, 并且Extra的值大多是Using index
。
(7) ALL: A full table scan is done
表示全表扫描, 这个类型的查询是性能最差的查询之一。通常来说, 我们的查询不应该出现ALL类型的查询, 因为这样的查询在数据量大的情况下, 严重降低数据库的性能。如果一个查询是ALL类型查询, 那么大多可以对相应的字段添加索引来避免。
mysql> explain select * from tb_hero where hero_name = '令狐冲';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_hero | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 16.67 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
possible_keys列
表示MySQL在查询时, 能够使用到的索引。注意, 即使有些索引在possible_keys中出现, 但是并不表示此索引会真正地被MySQL使用到。MySQL在查询时具体使用了哪些索引, 由key字段决定。
key列
这一列显示mysql实际采用哪个索引来优化对该表的访问。如果没有使用索引,则该列是NULL。
key_len列
表示查询优化器使用了索引的字节数,这个字段可以评估联合索引是否完全被使用, 或只有最左部分字段被使用到。
举例来说,tb_hero
表的联合索引idx_book_id_hero_name
由book_id
和hero_name
两个列组成,int类型占4字节,另外如果字段允许为NULL,需要1字节记录是否为NULL,通过结果中的key_len=5(tb_hero
.book_id
允许为NULL)可推断出查询使用了第一个列book_id
列来执行索引查找;再拿tb_book_hero
表联合主键PRIMARY KEY (book_id, hero_id)
举例,通过key_len=4(tb_book_hero
.book_id
不允许为NULL)可推断出查询使用了第一个列book_id
列来执行索引查找
mysql> explain select * from tb_hero where book_id = 2;
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_hero | NULL | ref | idx_book_id_hero_name | idx_book_id_hero_name | 5 | const | 2 | 100.00 | NULL |
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from tb_book_hero where book_id = 2;
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_book_hero | NULL | ref | PRIMARY | PRIMARY | 4 | const | 2 | 100.00 | NULL |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
key_len
的计算规则如下:
字符串:
- char(n): n字节长度
- varchar(n): 如果是
utf8
编码, 则是3n + 2
字节; 如果是utf8mb4
编码, 则是4n + 2
字节.
数值类型:
- TINYINT: 1字节
- SMALLINT: 2字节
- MEDIUMINT: 3字节
- INT: 4字节
- BIGINT: 8字节
时间类型
- DATE: 3字节
- TIMESTAMP: 4字节
- DATETIME: 8字节
字段属性:
- NULL属性占用一个字节
- 如果一个字段是NOT NULL的, 则没有此属性
再看下面的计算:
4 [book_id是int类型] + 1 [book_id允许为NULL] + (4 * 32 + 2) [hero_name是varchar32,且用的是utf8mb4编码] + 1 [hero_name允许为NULL] = 136
mysql> explain select * from tb_hero where book_id = 2 and hero_name = '令狐冲';
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | tb_hero | NULL | ref | idx_book_id_hero_name | idx_book_id_hero_name | 136 | const,const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
ref列
The ref column shows which columns or constants are compared to the index named in the key column to select rows from the table.
显示的是哪个字段或常数与key一起被使用
rows列
MySQL查询优化器根据统计信息, 估算SQL要查找到结果集需要扫描读取的数据行数,注意这个不是结果集里的行数。这个值非常直观显示SQL的效率好坏, 原则上rows越少越好。
Extra列
这一列展示的是额外信息。常见的重要值如下:
(1) Using index
表示查询在索引树中就可查找所需数据, 不用扫描表数据文件, 往往说明性能不错
mysql> explain select hero_id from tb_book_hero where book_id = 2;
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | tb_book_hero | NULL | ref | PRIMARY | PRIMARY | 4 | const | 2 | 100.00 | Using index |
+----+-------------+--------------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> explain select book_id from tb_book where author = '金庸';
+----+-------------+---------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | tb_book | NULL | ref | idx_author | idx_author | 131 | const | 3 | 100.00 | Using index |
+----+-------------+---------+------------+------+---------------+------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
(2) Using where
查询的列没有全部被索引覆盖
mysql> explain select book_id, book_name from tb_book where author = '金庸';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_book | NULL | ALL | idx_author | NULL | NULL | NULL | 5 | 60.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
(3) Using temporary
查询有使用临时表,一般出现于排序、分组、多表join、distinct查询等等。
举例子如下:tb_book
表对book_name
字段建立了唯一性索引,这时候distinct查询Extra列为Using index
; tb_hero
表的skill
字段上没有任何索引,这时候distinct查询Extra列为Using temporary
mysql> select distinct book_name from tb_book;
+-----------------------+
| book_name |
+-----------------------+
| 倚天屠龙记 |
| 多情剑客无情剑 |
| 射雕英雄传 |
| 笑傲江湖 |
| 绝代双骄 |
+-----------------------+
5 rows in set (0.00 sec)
mysql> explain select distinct book_name from tb_book;
+----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_book | NULL | index | uk_book_name | uk_book_name | 259 | NULL | 5 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select distinct skill from tb_hero;
+-----------------+
| skill |
+-----------------+
| 小李飞刀 |
| 独孤九剑 |
| 九阳神功 |
| 降龙十八掌 |
| 移花接玉 |
| 吸星大法 |
+-----------------+
6 rows in set (0.00 sec)
mysql> explain select distinct skill from tb_hero;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | SIMPLE | tb_hero | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using temporary |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------+
1 row in set, 1 warning (0.00 sec)
(4) Using filesort
表示MySQL不能通过索引顺序达到排序效果,需额外的排序操作,数据较小时在内存排序,否则需要在磁盘完成排序。这种情况下一般也是要考虑使用索引来优化的。
mysql> explain select book_id, hero_name from tb_hero order by hero_name;
+----+-------------+---------+------------+-------+---------------+-----------------------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+-----------------------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | tb_hero | NULL | index | NULL | idx_book_id_hero_name | 136 | NULL | 6 | 100.00 | Using index; Using filesort |
+----+-------------+---------+------------+-------+---------------+-----------------------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select book_id, hero_name from tb_hero order by book_id, hero_name;
+----+-------------+---------+------------+-------+---------------+-----------------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+-----------------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb_hero | NULL | index | NULL | idx_book_id_hero_name | 136 | NULL | 6 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+-----------------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
tb_hero
表上有联合索引INDEX idx_book_id_hero_name(book_id, hero_name) USING BTREE
但是order by hero_name
, 不能使用索引进行优化(下一篇博客会介绍联合索引的结构), 进而会产生Using filesort
如果将排序依据改为order by book_id, hero_name
, 就不会出现Using filesort
了。
(5) Select tables optimized away
比如下面的例子:
mysql> explain select min(book_id), max(book_id) from tb_book;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| 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 | Select tables optimized away |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
1 row in set, 1 warning (0.00 sec)
一文学会MySQL的explain工具的更多相关文章
- MySQL抓包工具:MySQL Sniffer【转】
本文来自:https://github.com/Qihoo360/mysql-sniffer 简介 MySQL Sniffer 是一个基于 MySQL 协议的抓包工具,实时抓取 MySQLServer ...
- mysql explain工具使用
explain工具可以确认执行计划是否良好,查询是否走了合理的索引.查询的执行计划,随着数据的变化也可能会有变化.调用方式:explain + [sql语句]. 另外,explain是有局限性的:1. ...
- 【转载】一个小时学会MySQL数据库
一个小时学会MySQL数据库 目录 一.数据库概要 1.1.发展历史 1.1.1.人工处理阶段 1.1.2.文件系统 1.1.3.数据库管理系统 1.2.常见数据库技术品牌.服务与架构 1.3.数 ...
- Mysql关键字Explain 性能优化神器
Explain工具介绍 使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析查询语句或是结构的性能瓶颈.在select语句之前增加explaion关键字,MySQL会在查询上设置一个标记,执行查 ...
- 巧用MySQL之Explain进行数据库优化
前记:很多东西看似简单,那是因为你并未真正了解它. Explain命令用于查看执行效果.虽然这个命令只能搭配select类型语句使用,如果你想查看update,delete类型语句中的索引效果,也不是 ...
- mysql监控管理工具--innotop
https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/innotop/innotop-1.9. ...
- MySQL性能剖析工具(pt-query-digest)【转】
这个工具同样来自percona-toolkit 该工具集合的其他工具 MySQL Slave异常关机的处理 (pt-slave-restart) 验证MySQL主从一致性(pt-table-chec ...
- MySQL Binlog 解析工具 Maxwell 详解
maxwell 简介 Maxwell是一个能实时读取MySQL二进制日志binlog,并生成 JSON 格式的消息,作为生产者发送给 Kafka,Kinesis.RabbitMQ.Redis.Goog ...
- 一文学会JVM性能优化
实战性能优化 1 重新认知JVM 之前我们画过一张图,是从Class文件到类装载器,再到运行时数据区的过程,现在咱们把这张图不妨丰富完善一下,展示了JVM的大体物理结构图. 执行引擎:用于执行JVM字 ...
随机推荐
- Let's GO(一)
近来开始学Go,留此博客以记录学习过程,顺便鞭策自己更加努力. 人生苦短,Let's GO! Let's GO(一) Let's GO(二) Let's GO(三) Let's GO(四) 简单介绍 ...
- SpringBoot入门详细教程
一.SpringBoot入门 1.SpringBoot简介 SpringBoot是整个Spring技术栈的整合,来简化Spring应用开发,约定大于配置,去繁从简,just run 就能创建一 个独立 ...
- 数据可视化基础专题(七):Pandas基础(六) 数据增删改以及相关操作
首先第一部还是导入 Pandas 与 NumPy ,并且要生成一个 DataFrame ,这里小编就简单的使用随机数的形式进行生成,代码如下: import numpy as np import pa ...
- java 基础(三) 搭建Java编译环境(树莓派)
安装需求1.JDK的安装2.PI4J的安装 JDK的安装1.首先到JDK的官网:https://www.oracle.com/technetwork/java/javase/downloads/ind ...
- [C#]正则表达式的基本用法
C#正则表达式的基本用法 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串.将匹配的子串替换或者从某个串中取出符合某个 ...
- 「美团面试系列」面试加分项,这样说你会JVM,面试官还能问什么
Java性能调优都是老生常谈的问题,特别当“糙快猛”的开发模式大行其道时,随着系统访问量的增加.代码的臃肿,各种性能问题便会层出不穷. 比如,下面这些典型的性能问题,你肯定或多或少都遇到过: 在进行性 ...
- 【Python学习笔记一】基础环境安装:idea+python
IDEA 安装 1.下载IDEA 官网下载地址: https://www.jetbrains.com/idea/ 2.安装的时候配置基本选择默认配置就行 参考链接:https://blog.csd ...
- 利用CloudFlare自动DDNS
注意要 仅限 DNS 获取咱的Key https://dash.cloudflare.com/profile 先在控制面板找到咱的目前IP,然后到Cloudflare中新建一个A记录,如:ddns.y ...
- 来了,来了,你们要的Nginx教程来了
一 Nginx简介 1.1 什么是Nginx Nginx是一个高性能的http和反向代理服务器,其特点是占用内存小,并发能力强.Nginx专为性能优化而开发,性能是其最重要的考量,能经受高负载的考验, ...
- Django坑_01
写在前面的话,不是我不想用 Linux 去运行 Django Windows 的坑很多,可是 Linux 太慢了 以后遇到的坑,我都会发出来,供大家参考的. python manage.py runs ...