MySQL 提供了一个 EXPLAIN 命令, 它可以对 SELECT 语句进行分析, 并输出 SELECT 执行的详细信息。通过该命令看出 mysql 是如何执行该条 sql 的,可以通过分析索引,表结构等方面来优化你的慢查询语句。
EXPLAIN 命令用法十分简单, 在 SELECT 语句前加上 Explain 就可以了, explain + sql 语句
, 例如:
EXPLAIN SELECT * FROM blog WHERE blog_id > 10;
执行结果如下
mysql> EXPLAIN SELECT * FROM blog WHERE blog_id > 10;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | blog | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 8 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
执行结果有如下的字段,分别是id、select_type、table、partitions、type、possible_keys、key、key_len、ref、rows、filtered、Extra等等,下面对这些字段出现的可能进行解释:
select 查询的标识符。如果在语句中没子查询或关联查询,只有唯一的select,每行都将显示1。否则,内层的select语句一般会顺序编号,对应于其在原始语句中的位置。
表示了查询的类型。主要是用于区别普通查询、联合查询、子查询等的复杂查询。
类型 | 说明 |
---|---|
simple | 简单子查询,不包含子查询和union |
primary | 包含union或者子查询,最外层的部分标记为primary |
subquery | 一般子查询中的子查询被标记为subquery,也就是位于select列表中的查询 |
dependent subquery | 相关子查询 |
derived | 派生表——该临时表是从子查询派生出来的,位于form中的子查询 |
union | 位于union中第二个及其以后的子查询被标记为union,第一个就被标记为primary,如果是union位于from中则标记为derived |
union result | 用来从匿名临时表里检索结果的select被标记为union |
dependent union | 顾名思义,首先需要满足UNION的条件,及UNION中第二个以及后面的SELECT语句,同时该语句依赖外部的查询 |
uncacheable subquery | 一个子查询的结果不能被缓存,必须重新评估外链接的第一行 |
表示查询涉及的表或衍生表。
mysql> explain select * from (select * from ( select * from t1 where id=2602) a) b;
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 2 | DERIVED | <derived3> | system | NULL | NULL | NULL | NULL | 1 | |
| 3 | DERIVED | t1 | const | PRIMARY,idx_t1_id | PRIMARY | 4 | | 1 | |
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
当from中有子查询的时候,表名是derivedN的形式,N指向子查询,也就是explain结果中的下一列
当有union result的时候,表名是union 1,2等的形式,1,2表示参与union的query id
注意:MySQL对待这些表和普通表一样,但是这些“临时表”是没有任何索引的。
表示MySQL在表中找到所需行的方式,又称“访问类型”。type 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 type 字段, 我们判断此次查询是 全表扫描 还是 索引扫描 等等。
注:表格性能从上到下是最坏到最佳顺序
类型 | 说明 |
---|---|
ALL | Full Table Scan, 最坏的情况, MySQL将遍历全表以找到匹配的行 |
index | 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index。见下方例子 |
range | 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中。当 type 是 range 时, 那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个索引长度。见下方例子 |
ref | 哪些列或常量与key一起使用,被用于查找索引列上的值。此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了最左前缀规则索引的查询。见下方例子 |
eq-ref | 最多只返回一条符合条件的记录。使用唯一性索引或主键查找时会发生 (高效)。此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是 =, 查询效率较高。见下方例子 |
const | 针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可。见下方例子 |
system | 表中只有一条数据, 这个类型是特殊的 const 类型. |
mysql> EXPLAIN SELECT update_time FROM blog;
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | blog | NULL | index | NULL | updatetime | 6 | NULL | 14 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
上面的例子中, 我们查询的 update_time 字段恰好是一个索引, 因此我们直接从索引中获取数据就可以满足查询的需求了, 而不需要查询表中的数据. 因此这样的情况下, type 的值是 index, 并且 Extra 的值是 Using index.
mysql> EXPLAIN SELECT * FROM blog WHERE blog_id BETWEEN 2 AND 8;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | blog | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 4 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
注:此处blog_id 为blog 表的主键
mysql> EXPLAIN SELECT * FROM blog, sys_user WHERE blog.user_id = sys_user.id AND sys_user.id = 13;
+----+-------------+----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | sys_user | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 1 | SIMPLE | blog | NULL | ref | userid | userid | 4 | const | 13 | 100.00 | NULL |
+----+-------------+----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
注:此处user_id为blog表里的一个索引字段(Normal 类型),sys_user表的主键为id。
mysql> EXPLAIN SELECT * FROM blog, sys_user WHERE blog.user_id = sys_user.id;
+----+-------------+----------+------------+--------+---------------+---------+---------+-------------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+--------+---------------+---------+---------+-------------------------+------+----------+-------+
| 1 | SIMPLE | blog | NULL | ALL | userid | NULL | NULL | NULL | 14 | 100.00 | NULL |
| 1 | SIMPLE | sys_user | NULL | eq_ref | PRIMARY | PRIMARY | 4 | my_blog_db.blog.user_id | 1 | 100.00 | NULL |
+----+-------------+----------+------------+--------+---------------+---------+---------+-------------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM blog WHERE blog_id = 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | blog | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
匹配的分区。
此次查询中可能选用的索引。
表示 MySQL 在查询时, 能够使用到的索引。注意, 即使有些索引在 possible_keys 中出现, 但是并不表示此索引会真正地被 MySQL 使用到。MySQL 在查询时具体使用了哪些索引, 由 key 字段决定。
此字段是 MySQL 在当前查询时所真正使用到的索引。如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
表示索引中使用的字节数,如果键是NULL,则长度为NULL。这个字段可以评估组合索引是否完全被使用, 或只有最左部分字段被使用到。
key_len 的计算规则如下:
字符串
数值类型:
时间类型
字段属性: NULL 属性 占用一个字节. 如果一个字段是 NOT NULL 的, 则没有此属性。
粟子:
mysql> EXPLAIN SELECT * FROM sys_user WHERE id < 13 and user_name = "admin" and email = "[email protected]";
+----+-------------+----------+------------+-------+---------------------------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------------------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | sys_user | NULL | range | PRIMARY,id_username_email_index | PRIMARY | 4 | NULL | 1 | 5.00 | Using where |
+----+-------------+----------+------------+-------+---------------------------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
上面的例子是从表 sys_user 中查询指定的内容, 而从此表的建表语句中可以知道, 表 sys_user 有一个联合索引:
UNIQUE KEY `id_username_email_index` (`id`,`user_name`,`email`) USING BTREE
不过此查询语句 WHERE id < 13 and user_name = "admin" and email = "[email protected]"
中, 因为先进行 id 的范围查询, 而根据 最左前缀匹配 原则, 当遇到范围查询时, 就停止索引的匹配, 因此实际上我们使用到的索引的字段只有 id, 因此在 EXPLAIN 中, 显示的 key_len 为 4. 因为 id 字段是 int 占用 4 字节。上面因为 最左前缀匹配 原则, 我们的查询仅仅使用到了联合索引的 id 字段, 因此效率不算高。
更多关于key_len 的计算可以看看这篇文章:Explain之key_len长度计算
哪个字段或常数与 key 一起被使用
rows 也是一个重要的字段. MySQL 查询优化器根据统计信息, 估算 SQL 要查找到结果集需要扫描读取的数据行数。这个值非常直观显示 SQL 的效率好坏, 原则上 rows 越少越好。
表示此查询条件所过滤的数据的百分比。
包含MySQL解决查询的详细信息, MySQL查询优化器执行查询的过程中对查询计划的重要补充信息。
有以下几种情况: