每日MySQL之020:使用EXPLAIN查看SQL的执行计划

时间:2021-07-31 03:55:37
MySQL中要查看执行计划,可以使用EXPLAIN命令,EXPLAIN 使用的对象是 SELECT, DELETE, INSERT, REPLACE, 和 UPDATE 语句。将EXPLAIN换成DESCRIBE也行,两者含义一样,但实际操作中一般用DESCRIBE查看表结构,用EXPLAIN查看执行计划。输出的结果不是图形化的,而是一张表。对于每个SELECT语句中的每个表,EXPLAIN都会返回一行。返回的顺序就是MySQL读取表的顺序。MySQL使用nested-loop join来完成所有的join工作。

先来看两个简单的例子,第一个SELECT语句访问一张表,只有一行输出。第二个SELECT语句访问两张表,就有两行输出,table列指定了访问的表名:

mysql> EXPLAIN SELECT id, age from t1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT t1.id, t1.age, t2.name FROM t1,t2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | NULL |
| 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.01 sec)

再详细看一下各个字段的含义,假设我们有以下三张表:

mysql> CREATE TABLE `t1` (id int, age int, KEY `idx1` (`id`));
mysql> CREATE TABLE `t2` ( `id` int(11),`name` char(15));
mysql> CREATE TABLE `t3` (id int not null primary key, name char(30));

mysql> INSERT INTO `t1` VALUES (1001,24),(1002,25),(1003,43),(1004,44),(1006,31),(1005,23);
mysql> INSERT INTO `t2` VALUES (1002,'name1'),(1005,'name2'),(1007,'name3'),(1010,'name4');
mysql> INSERT INTO `t3` VALUES (10086, 'Mobile'),(10011, 'Unicom'),(95583,'CommercialBank'),(12355,'taxiBue'),(1010,'aa');

mysql> ANALYZE TABLE t1;
mysql> ANALYZE TABLE t2;
mysql> ANALYZE TABLE t3;

mysql> select * from t1;
+------+------+
| id | age |
+------+------+
| 1001 | 24 |
| 1002 | 25 |
| 1003 | 43 |
| 1004 | 44 |
| 1006 | 31 |
| 1005 | 23 |
+------+------+
6 rows in set (0.01 sec)

mysql> select * from t2;
+------+-------+
| id | name |
+------+-------+
| 1002 | name1 |
| 1005 | name2 |
| 1007 | name3 |
| 1010 | name4 |
+------+-------+
4 rows in set (0.00 sec)

mysql> select * from t3;
+-------+----------------+
| id | name |
+-------+----------------+
| 1010 | aa |
| 10011 | Unicom |
| 10086 | Mobile |
| 12355 | taxiBue |
| 95583 | CommercialBank |
+-------+----------------+
5 rows in set (0.00 sec)

id (JSON name: select_id)
SELECT ID, 查询语句中SELECT的编号。如果是union的结果,可能为空值,这时table column显示的是<unionM,N>。 

示例1: 第一个语句有两个SELECT语句,所以有两个id,第二个语句有三个SELECT,所以有三个id,UNION RESULT的id为空:

mysql> EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2;
+----+--------------+------------+
| id | select_type | table |
+----+--------------+------------+
| 1 | PRIMARY | t1 |
| 2 | UNION | t2 |
|NULL| UNION RESULT | <union1,2> |
+----+--------------+------------+
3 rows in set, 1 warning (0.00 sec)

mysql> EXPLAIN SELECT * FROM t1 UNION SELECT * FROM t2 UNION SELECT * FROM t3;
+----+--------------+--------------+
| id | select_type | table |
+----+--------------+--------------+
| 1 | PRIMARY | t1 |
| 2 | UNION | t2 |
| 3 | UNION | t3 |
|NULL| UNION RESULT | <union1,2,3> |
+----+--------------+--------------+
4 rows in set, 1 warning (0.00 sec)


select_type (JSON name: none)
SIMPLE:简单查询(不使用UNION或者子查询)
PRIMARY: 最外边SELECT
UNION: UNION中第二个及以后的SELECT

参考示例1。

table (JSON name: table_name)
表名。

partitions (JSON name: partitions)
分区表的分区名,对于非分区表,为NULL

type (JSON name: access_type)
Join type,可以参考:链接http://blog.csdn.net/qingsong3333/article/details/77149845

possible_keys (JSON name: possible_keys)
可能选择的索引

key (JSON name: key)
最终选择的索引

示例2,第一条SELECT没有使用索引, 第二条使用了索引idx1,第三条使用了PRIMARY KEY。

mysql> EXPLAIN SELECT * FROM t1 where id < 1005;
+----+-------------+-------+...+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table |...| possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+...+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 |...| idx1 | NULL | NULL | NULL | 6 | 66.67 | Using where |
+----+-------------+-------+...+---------------+------+---------+------+------+----------+-------------+

mysql> EXPLAIN SELECT * FROM t1 where id > 1004;
+----+-------------+-------+...+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table |...| possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+...+---------------+------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | t1 |...| idx1 | idx1 | 5 | NULL | 2 | 100.00 | Using index condition |
+----+-------------+-------+...+---------------+------+---------+------+------+----------+-----------------------+

mysql> EXPLAIN SELECT * FROM t3 where id > 10010;
+----+-------------+-------+...+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table |...| possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+...+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t3 |...| PRIMARY | PRIMARY | 4 | NULL | 4 | 100.00 | Using where |
+----+-------------+-------+...+---------------+---------+---------+------+------+----------+-------------+

key_len (JSON name: key_length)
key的长度

rows (JSON name: rows)
MySQL认为完成查询需要访问的行数,只是个估计值。

filtered (JSON name: filtered)
被条件过滤掉的百分比。That is, rows shows the estimated number of rows examined and rows × filtered / 100
shows the number of rows that will be joined with previous tables.

Extra (JSON name: none)
额外说明

另外,可以设置 FORMAT 参数,如果设为JSON,则显示为JSON格式:
mysql> EXPLAIN FORMAT=JSON SELECT id, age from t1 \G
*************************** 1. row ***************************
EXPLAIN: {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "2.20"
},
"table": {
"table_name": "t1",
"access_type": "ALL",
"rows_examined_per_scan": 6,
"rows_produced_per_join": 6,
"filtered": "100.00",
"cost_info": {
"read_cost": "1.00",
"eval_cost": "1.20",
"prefix_cost": "2.20",
"data_read_per_join": "96"
},
"used_columns": [
"id",
"age"
]
}
}
}
1 row in set, 1 warning (0.00 sec)