摘要:
- 本篇是根据官网中的每个一点来翻译、举例、验证的;英语不好,所以有些话语未必准确,请自行查看官网,若有些点下面没有例子的是因为当时一下子没有想出那么多来,如果大家有遇上好的例子,欢迎在下面留言我持续更新
- 查看执行计划的关键EXPLAIN
- 版本MYSQL5.6,用到的库是官网例子sakila,自行下载导入
由于要把每个点都翻译出来,还需要举例,所以需要一定的时间,本人先把架构理出来,然后逐个点开始
官网地址:http://dev.mysql.com/doc/refman/5.6/en/explain-output.html
EXPLAIN语句返回MYSLQ的执行计划,通过他返回的信息,我们能了解到MYSQL优化器是如何执行SQL语句的,通过分析他能帮助你提供优化的思路。
语法
MYSQL 5.6.3以前只能EXPLAIN SELECT; MYSQL5.6.3以后就可以EXPLAIN SELECT,UPDATE,DELETE
- EXPLAIN 语法例子:
mysql> explain select customer_id,a.store_id,first_name,last_name, b.manager_staff_id from customer a left join store b on a.store_id=b.store_id;
+----+-------------+-------+--------+---------------+---------+---------+-------------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-------------------+------+-------+
| 1 | SIMPLE | a | ALL | NULL | NULL | NULL | NULL | 599 | NULL |
| 1 | SIMPLE | b | eq_ref | PRIMARY | PRIMARY | 1 | sakila.a.store_id | 1 | NULL |
+----+-------------+-------+--------+---------------+---------+---------+-------------------+------+-------+
2 rows in set
- EXPLAIN还有一种语法,类似于desc
mysql> explain actor;
+-------------+----------------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+----------------------+------+-----+-------------------+-----------------------------+
| actor_id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| first_name | varchar(45) | NO | | NULL | |
| last_name | varchar(45) | NO | MUL | NULL | |
| last_update | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+----------------------+------+-----+-------------------+-----------------------------+
4 rows in set
EXPLAIN的输出
EXPLAIN主要包含以下信息:
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 |
id (JSON name: select_id)
SQL查询中的序列号。
select_type (JSON name: none)
查询的类型,可以是下表的任何一种类型:
select_type Value | JSON Name | Meaning |
---|---|---|
SIMPLE | None | 简单查询(不适用union和子查询的) |
PRIMARY | None | 最外层的查询 |
UNION | None | UNION中的第二个或者后面的SELECT语句 |
DEPENDENT UNION | dependent (true) | UNION中的第二个或者后面的SELECT语句,依赖于外部查询 |
UNION RESULT | union_result | UNION结果 |
SUBQUERY | None | 子查询中的第一个SELECT语句 |
DEPENDENT SUBQUERY | dependent (true) | 子查询中的第一个SELECT语句,依赖于外部查询 |
DERIVED | None | 派生表的SELECT(FROM子句的子查询) |
MATERIALIZED | materialized_from_subquery | 物化子查询 |
UNCACHEABLE SUBQUERY | cacheable (false) | 对于该结果不能被缓存,必须重新评估外部查询的每一行子查询 |
UNCACHEABLE UNION | cacheable (false) | UNION中的第二个或者后面的SELECT语句属于不可缓存子查询 (see UNCACHEABLE SUBQUERY) |
查询类型例子:
1、SIMPLE 简单查询(不适用union和子查询的)
mysql> explain select * from staff;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | staff | ALL | NULL | NULL | NULL | NULL | 2 | NULL |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set
2、PRIMARY 最外层的查询
mysql> explain select * from (select last_name,first_name from customer) a;
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 599 | NULL |
| 2 | DERIVED | customer | ALL | NULL | NULL | NULL | NULL | 599 | NULL |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
2 rows in set
3、UNION UNION中的第二个或者后面的SELECT语句
mysql> explain select first_name,last_name from customer a where customer_id=1 union select first_name,last_name from customer b where customer_id=2;
+------+--------------+------------+-------+---------------+---------+---------+-------+------+-----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+------------+-------+---------------+---------+---------+-------+------+-----------------+
| 1 | PRIMARY | a | const | PRIMARY | PRIMARY | 2 | const | 1 | NULL |
| 2 | UNION | b | const | PRIMARY | PRIMARY | 2 | const | 1 | NULL |
| NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------+------------+-------+---------------+---------+---------+-------+------+-----------------+
3 rows in set
4、DEPENDENT UNION UNION中的第二个或者后面的SELECT语句,依赖于外部查询
mysql> explain select * from customer where customer_id in(select customer_id from customer a where customer_id=1 union all select customer_id from customer b where customer_id=2);
+------+--------------------+------------+-------+---------------+---------+---------+-------+------+-----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------------+------------+-------+---------------+---------+---------+-------+------+-----------------+
| 1 | PRIMARY | customer | ALL | NULL | NULL | NULL | NULL | 599 | Using where |
| 2 | DEPENDENT SUBQUERY | a | const | PRIMARY | PRIMARY | 2 | const | 1 | Using index |
| 3 | DEPENDENT UNION | b | const | PRIMARY | PRIMARY | 2 | const | 1 | Using index |
| NULL | UNION RESULT | <union2,3> | ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------------+------------+-------+---------------+---------+---------+-------+------+-----------------+
4 rows in set
5、UNION RESULT UNION结果
mysql> explain select * from staff union select * from staff;
+------+--------------+------------+------+---------------+------+---------+------+------+-----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+------------+------+---------------+------+---------+------+------+-----------------+
| 1 | PRIMARY | staff | ALL | NULL | NULL | NULL | NULL | 2 | NULL |
| 2 | UNION | staff | ALL | NULL | NULL | NULL | NULL | 2 | NULL |
| NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------+------------+------+---------------+------+---------+------+------+-----------------+
3 rows in set
6、SUBQUERY 子查询中的第一个SELECT语句
mysql> explain select customer_id from customer where store_id =
(select store_id from store where store_id=1);
+----+-------------+----------+-------+-----------------+-----------------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+-----------------+-----------------+---------+-------+------+--------------------------+
| 1 | PRIMARY | customer | ref | idx_fk_store_id | idx_fk_store_id | 1 | const | 326 | Using where; Using index |
| 2 | SUBQUERY | store | const | PRIMARY | PRIMARY | 1 | const | 1 | Using index |
+----+-------------+----------+-------+-----------------+-----------------+---------+-------+------+--------------------------+
2 rows in set
有兴趣的可以去把=号换成
in
试试
7、DERIVED 派生表的SELECT(FROM子句的子查询)
mysql> explain select * from (select * from customer) a;
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 599 | NULL |
| 2 | DERIVED | customer | ALL | NULL | NULL | NULL | NULL | 599 | NULL |
+----+-------------+------------+------+---------------+------+---------+------+------+-------+
2 rows in set
8、其它如物化视图等查询自己去造例子去
table(JSON name: table_name)
显示这一行的数据是关于哪张表的,也可以是下列值之一:
unionM,N: The row refers to the union of the rows with id values of M and N.
derivedN: The row refers to the derived table result for the row with an id value of N. A derived table may result, for example, from a subquery in the FROM clause.
subqueryN: The row refers to the result of a materialized subquery for the row with an id value of N.
partitions (JSON name: partitions)
分区中的记录将被查询相匹配。显示此列仅在使用分区关键字。该值为NULL对于非分区表。
type (JSON name: access_type)
EXPLAIN输出的类型列描述了表的连接方法。下面的列表介绍了连接类型,从最好的类型到最差的命令:
1、system
这是const的一个特例联接类型。表只有一行(=系统表)。
mysql> explain select * from (select * from customer where customer_id=1) a;
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | NULL |
| 2 | DERIVED | customer | const | PRIMARY | PRIMARY | 2 | const | 1 | NULL |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------+
2 rows in set
2、const
表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!
const用于用常数值比较PRIMARY KEY或UNIQUE索引的所有部分时。在下面的查询中,tbl_name可以用于const表:
SELECT * from tbl_name WHERE primary_key=1;
SELECT * from tbl_name WHERE primary_key_part1=1和 primary_key_part2=2;
3、eq_ref
对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型。它用在一个索引的所有部分被联接使用并且索引是UNIQUE或PRIMARY KEY。
eq_ref可以用于使用= 操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。
在下面的例子中,MySQL可以使用eq_ref联接来处理ref_tables:
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
# 相对于下面的ref区别就是它使用的唯一索引,即主键或唯一索引,而ref使用的是非唯一索引或者普通索引。id是主键
mysql> explain select a.*,b.* from testa a,testb b where a.id=b.id
;
+----+-------------+-------+--------+---------------+---------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-------------+------+-------------+
| 1 | SIMPLE | b | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
| 1 | SIMPLE | a | eq_ref | PRIMARY | PRIMARY | 4 | sakila.b.id | 1 | NULL |
+----+-------------+-------+--------+---------------+---------+---------+-------------+------+-------------+
2 rows in set
4、ref
对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY(换句话说,如果联接不能基于关键字选择单个行的话),则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。
ref可以用于使用=或<=>操作符的带索引的列。
在下面的例子中,MySQL可以使用ref联接来处理ref_tables:
SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
# 使用非唯一性索引或者唯一索引的前缀扫描,返回匹配某个单独值的记录行。name有非唯一性索引
mysql> explain select * from testa where name=‘aaa‘;
+----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------+
| 1 | SIMPLE | testa | ref | idx_name | idx_name | 33 | const | 2 | Using index condition |
+----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------+
1 row in set
mysql> explain select a.*,b.* from testa a,testb b where a.name=b.cname;
+----+-------------+-------+------+---------------+----------+---------+----------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+----------------+------+-------------+
| 1 | SIMPLE | b | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
| 1 | SIMPLE | a | ref | idx_name | idx_name | 33 | sakila.b.cname | 1 | NULL |
+----+-------------+-------+------+---------------+----------+---------+----------------+------+-------------+
2 rows in set
5、 fulltext
使用FULLTEXT索引进行联接。
6、ref_or_null
该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。在解决子查询中经常使用该联接类型的优化。
在下面的例子中,MySQL可以使用ref_or_null联接来处理ref_tables:
SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
mysql> explain select * from (select cusno from testa t1,testb t2 where t1.id=t2.id) t where cusno =2 or cusno is null;
+----+-------------+------------+-------------+---------------+-------------+---------+--------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------------+---------------+-------------+---------+--------------+------+--------------------------+
| 1 | PRIMARY | <derived2> | ref_or_null | <auto_key0> | <auto_key0> | 5 | const | 2 | Using where; Using index |
| 2 | DERIVED | t2 | index | PRIMARY | PRIMARY | 4 | NULL | 1 | Using index |
| 2 | DERIVED | t1 | eq_ref | PRIMARY | PRIMARY | 4 | sakila.t2.id | 1 | NULL |
+----+-------------+------------+-------------+---------------+-------------+---------+--------------+------+--------------------------+
3 rows in set
此处按照官网的格式未测试出例子来,若有例子的请留言,我测试更新
7、index_merge
该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。
此处按照官网的格式未测试出例子来,若有例子的请留言,我测试更新
8、unique_subquery
unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。
该类型替换了下面形式的IN子查询的ref:
value IN (SELECT primary_key FROM single_table WHERE some_expr)
此处按照官网的格式未测试出例子来,若有例子的请留言,我测试更新
9、index_subquery
该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:
value IN (SELECT key_column FROM single_table WHERE some_expr)
此处按照官网的格式未测试出例子来,若有例子的请留言,我测试更新
10、range
只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。
当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
11、index
索引类型与ALL类型一样,除了它是走索引树扫描的,它有两种方式:
如果该覆盖索引能满足查询的所有数据,那仅仅扫描这索引树。在这种情况下,Extra
列就会显示用Using index
。一般仅仅用索引是扫描的比ALL扫描的要快,因为索引树比表数据小很多。
全表扫描被用到从索引中去读取数据, Extra
列就不会显示用Using index
。
如果查询仅仅是索引列,那MySQL会这个index
索引类型
mysql> alter table testa add primary key p_id(id);
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index idx_name on testa(name);
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
mysql> insert into testa values(2,2,‘aaa‘);
Query OK, 1 row affected
# 因为查询的列name上建有索引,所以如果这样type走的是index
mysql> explain select name from testa;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
| 1 | SIMPLE | testa | index | NULL | idx_name | 33 | NULL | 2 | Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
1 row in set
# 因为查询的列cusno没有建索引,或者查询的列包含没有索引的列,这样查询就会走ALL扫描,如下:
mysql> explain select cusno from testa;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | testa | ALL | NULL | NULL | NULL | NULL | 2 | NULL |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set
# *包含有未见索引的列
mysql> explain select * from testa;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | testa | ALL | NULL | NULL | NULL | NULL | 2 | NULL |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set
12、all
对于每个来自于先前的表的行组合,进行完整的表扫描。如果表是第一个没标记const的表,这通常不好,并且通常在它情况下很差。通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或列值被检索出。
possible_keys (JSON name: possible_keys)
possible_keys列指出MySQL能使用哪个索引在该表中找到行。而下面的key是MYSQL实际用到的索引,这意味着在possible_keys中的是计划中的,而key是实际的,也就是计划中有这个索引,实际执行时未必能用到。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询。
例子参考下面
key (JSON name: key)
key列显示MySQL实际决定使用的键(索引)。
如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。
例子参考下面
key_len (JSON name: key_length)
key_len列显示MySQL决定使用的键长度。如果KEY键是NULL,则长度为NULL。
使用的索引的长度。在不损失精确性的情况下,长度越短越好.
例子参考下面
ref (JSON name: ref)
ref列显示使用哪个列或常数与key一起从表中选择行。 它显示的是列的名字(或单词“const”),MySQL将根据这些列来选择行。
例子参考下面
rows (JSON name: rows)
rows列显示MySQL认为它执行查询时必须检查的行数。
例子参考下面
filtered (JSON name: filtered)
如果你用EXPLAIN EXTENDED将会展示出这列filtered(MySQL5.7缺省就会输出filtered),它指返回结果的行占需要读到的行(rows列的值)的百分比。按说filtered是个非常有用的值,因为对于join操作,前一个表的结果集大小直接影响了循环的次数。但是我的环境下测试的结果却是,filtered的值一直是100%,也就是说失去了意义。
上面部分EXPLAIN展示的列的例子:
mysql> alter table testa add primary key p_id(id);
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index idx_name on testa(name);
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
mysql> insert into testa values(2,2,‘aaa‘);
Query OK, 1 row affected
# 下面possible_keys可能会用到的索引有主键和我建的索引,但是key实际用到的是主键,主键长度是4,ref用的列的名字(或单词“const”,此处用的是常量const,速度快,rows扫描的只有1行
mysql> explain select cusno from testa where id=2 and name=‘aaa‘;
+----+-------------+-------+-------+------------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+------------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | testa | const | PRIMARY,idx_name | PRIMARY | 4 | const | 1 | NULL |
+----+-------------+-------+-------+------------------+---------+---------+-------+------+-------+
1 row in set
# 下面虽然name有索引,但是查询的列cusno没有索引,这时mysql计划possible_keys有索引,但实际key未走索引,若果cusno换成有索引的列,参照下面。
mysql> explain select cusno from testa where name=‘aaa‘;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | testa | ALL | idx_name | NULL | NULL | NULL | 1 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set
# id是有主键索引的,这时实际key已经走索引了,若果查询列换成既有索引的列也有无索引的列,参照下面
mysql> explain select id from testa where name=‘aaa‘;
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
| 1 | SIMPLE | testa | ref