本文来自我的github pages博客http://galengao.github.io/ 即www.gaohuirong.cn
摘要:
- 本篇是根据官网中的每个一点来翻译、举例、验证的;英语不好,所以有些话语未必准确,请自行查看官网,若有些点下面没有例子的是因为当时一下子没有想出那么多来,如果大家有遇上好的例子,欢迎在下面留言我持续更新
- 查看执行计划的关键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