Explain简称执行计划,可以模拟SQL语句,来分析查询语句或者表结构是否有性能瓶颈。
Explain的作用有哪些,可以看到哪些?
可以看到表的读取顺序,数据读取操作的操作类型,哪些索引可以使用,哪些索引被实际应用,表之间的引用,每张表有多少行被优化器查询。
准备工作
DROP TABLE IF EXISTS `t1`;
CREATE TABLE `t1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`other_column` varchar(30) NOT NULL DEFAULT '',
`other_column2` varchar(30) NOT NULL DEFAULT '',
`other_column3` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx` (`other_column`),
KEY `u_idx` (`other_column2`,`other_column3`),
KEY `u_idx2` (`other_column`,`other_column2`,`other_column3`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t1
-- ----------------------------
INSERT INTO `t1` VALUES ('1', 'A', 'D', 'L');
INSERT INTO `t1` VALUES ('2', 'B', 'E', 'M');
INSERT INTO `t1` VALUES ('3', 'C', '', 'N');
INSERT INTO `t1` VALUES ('4', '', 'F', '');
INSERT INTO `t1` VALUES ('5', 'F', 'G', 'O');
INSERT INTO `t1` VALUES ('6', 'A', 'H', 'P');
-- ----------------------------
-- Table structure for t2
-- ----------------------------
DROP TABLE IF EXISTS `t2`;
CREATE TABLE `t2` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`other_column` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx` (`other_column`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t2
-- ----------------------------
INSERT INTO `t2` VALUES ('1', 'C');
INSERT INTO `t2` VALUES ('2', 'D');
INSERT INTO `t2` VALUES ('3', 'E');
INSERT INTO `t2` VALUES ('4', '');
INSERT INTO `t2` VALUES ('5', 'G');
-- ----------------------------
-- Table structure for t3
-- ----------------------------
DROP TABLE IF EXISTS `t3`;
CREATE TABLE `t3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`other_column` varchar(30) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t3
-- ----------------------------
INSERT INTO `t3` VALUES ('1', 'F');
INSERT INTO `t3` VALUES ('2', 'G');
INSERT INTO `t3` VALUES ('3', 'H');
INSERT INTO `t3` VALUES ('4', '');
INSERT INTO `t3` VALUES ('5', 'I');
在使用Explain分析SQL语句之后,会出现这些列,分别是id、type、tabl、select_type、possible_keys、key、key_len、ref、rows、Extra。下面就来拿几张表和语句全面说明一下。
id :select查询的一个序列号,包含一组数字,表示查询中执行select子句或者操作表的顺序(有三种情况)。
①id相同表示mysql内部的查询优化器执行命令,也就是加载表的顺序的,从上到下,也就是先后加载了t1,t2,t3,当然也有可能顺序不是这样。
EXPLAIN SELECT t2.* FROM t1,t2,t3 WHERE t1.id = t2.id AND t1.id = t3.id AND t1.other_column = '';
+----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------------+
| 1 | SIMPLE | t1 | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where |
| 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | |
| 1 | SIMPLE | t3 | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------------+
②id不同
EXPLAIN SELECT t2.* FROM t2 WHERE id = (SELECT id FROM t1 WHERE id = (SELECT t3.id FROM t3 WHERE t3.other_column = ''));
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
| 1 | PRIMARY | t2 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
| 2 | SUBQUERY | t1 | const | PRIMARY | PRIMARY | 4 | | 1 | Using index |
| 3 | SUBQUERY | t3 | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。也就是先执行子查询查t3的表语句,再t1。
③id相同存在,不同也存在。
EXPLAIN SELECT t2.* FROM(SELECT t3.id FROM t3 WHERE t3.other_column = '') s1,t2 WHERE s1.id = t2.id;
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | t2 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
| 2 | DERIVED | t3 | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------+
分析结果可看出,先走id最大的2,也就是先走括号里面的查t3表的语句。走完查t3后,顺序执行,有一个,derived是衍生的意思,意思是在执行完t3查询后的s1虚表基础上,中的2,就是id为2的。最后执行的查t2表。
select_type(数据读取操作的操作类型)
常见常用的6个值分别是:SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION、UNION RESULT,主要是告诉开发者查询的类型,为了区别是普通查询、联合查询、子查询等复杂的查询。
SIMPLE:最简单的查询,查询中不包含子查询或者UNION。
PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY,也就是最后加载的那个。(如上面查询分析语句)
SUBQUERY:在SELECT或者WHERE列表中包含了子查询
DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生)Mysql会递归执行这些子查询,把结果放在临时表里(如上面查询分析语句)
UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED
UNION RESULT:两种UNION语句的合并。
table(表示查询涉及的表或衍生表)
type
反应的结果和mysql是否优化过,是否是最佳状态息息相关。
常见的大概7种:
从最好到最差的结果依次如下:
system > const > eq_ref > ref > range > index > ALL
system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计,且只能用于myisam和memory表。如果是Innodb引擎表,type列在这个情况通常都是all或者index。
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主题置于WHERE列表中,Mtsql就能将该查询转换为一个常量。如下
explain select * from (select * from t1 where id =1) d1;
+----+-------------+------------+--------+---------------+---------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+---------------+---------+---------+------+------+-------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 2 | DERIVED | t1 | const | PRIMARY | PRIMARY | 4 | | 1 | |
+----+-------------+------------+--------+---------------+---------+---------+------+------+-------+
eq_ref :唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或者唯一索引扫描
explain select * from t1,t2 where t1.id = t2.id;
+----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------+
| 1 | SIMPLE | t1 | ALL | PRIMARY | NULL | NULL | NULL | 5 | |
| 1 | SIMPLE | t2 | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | |
+----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------+
ref:非唯一性索引或非主键索引扫描,或者是使用了 最左前缀 规则索引的查询,返回匹配某个单独值得所有行。本质上也是一种索引访问,它返回所有匹配某个单独值得行,然而,它可能会找到多个符合条件的行,所以他应该属于朝赵和扫描的混合体。
create index idx on t1(other_column);
explain select * from t1 where other_column ='A';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
| 1 | SIMPLE | t1 | ref | idx | idx | 92 | const | 1 | Using where |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。一般就是在你的WHERE语句中出现了BETWEEN、<、>、IN等的查询,这种范围扫描索引比全表扫描更好,因为它只需要开始于索引的某一点,而结束语另一点不用扫描全部索引。
explain select * from t1 where id between 2 and 5;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 3 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
index: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据,比ALL稍微好点,如果表数据不小,必须优化。
index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index.
explain select id from t1;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | index | NULL | PRIMARY | 4 | NULL | 6 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
ALL: 表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免.
下面是一个全表扫描的例子, 可以看到, 在全表扫描时, possible_keys 和 key 字段都是 NULL, 表示没有使用到索引, 并且 rows 十分巨大, 因此整个查询效率是十分低下的.
explain select * from t2 where other_column = '';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | t2 | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
possible_keys 和 key
显示可能应用在这张表中的索引,一个或者多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。也就是说possible_keys是推测可能用到哪些索引,而key是实际用到的。这里例子很多,不举例
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度。即key_len是根据表定义计算而得,不是通过表内检索出的
ref
显示索引的哪一段被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows
根据表统计信息及搜索选用情况,大致估算出找到所需的记录所需要读取的行数,当然越小越好
extra(列返回的描述的意义,这里只说常见的)
Using filesort:看到这个的时候,查询就需要优化了。说明Mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。Mysql中无法利用索引完成的排序操作称之为文件排序。Mysql需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。
explain select other_column from t1 where other_column = 'A' order by other_column3;
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | t1 | ref | idx | idx | 92 | const | 1 | Using where; Using filesort |
+----+-------------+-------+------+---------------+------+---------+-------+------+-----------------------------+
优化案例:
create index u_idx2 on t1(other_column,other_column2,other_column3);
EXPLAIN SELECT other_column FROM t1 WHERE other_column = 'A'ORDER BY other_column2,other_column3;
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | t1 | ref | idx,u_idx2 | u_idx2 | 92 | const | 1 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。表示相应的select操作中使用了覆盖索引(Coverindex ing),避免访问了表的数据航,效果理想!如果同时出现using where,表示索引被用来执行索引键值的查找;如果没有同时出现using where,表示索引用来读取数据而非执行查找动作。
覆盖索引的含义: 就是select的数据列只用从索引中就能够取得,不必读取数据行,Mysql可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。注意的是,如果要使用覆盖索引,一定要注意select列表中只读取出需要的列,而不是select *,因为如果将所有字段一起做索引会导致索引文件过大,降低查询性能。
EXPLAIN SELECT other_column FROM t1 WHERE other_column in ('A','B','C') group by other_column,other_column2;
+----+-------------+-------+-------+---------------+--------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+--------------------------+
| 1 | SIMPLE | t1 | range | idx,u_idx2 | u_idx2 | 92 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+---------------+--------+---------+------+------+--------------------------+
EXPLAIN SELECT other_column,other_column2,other_column3 FROM t1;
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------------+
| 1 | SIMPLE | t1 | index | NULL | u_idx2 | 276 | NULL | 6 | Using index |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-------------+
Using temporary:看到这个的时候,查询需要优化了。这里,Mysql需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上和GROUP BY上,拖慢与sql查询。
EXPLAIN SELECT other_column FROM t1 WHERE other_column in ('A','B','C') group by other_column3;
+----+-------------+-------+-------+---------------+--------+---------+------+------+-----------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-----------------------------------------------------------+
| 1 | SIMPLE | t1 | range | idx,u_idx2 | u_idx2 | 92 | NULL | 3 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+-------+-------+---------------+--------+---------+------+------+-----------------------------------------------------------+
优化案例:
EXPLAIN SELECT other_column FROM t1 WHERE other_column in ('A','B','C') group by other_column,other_column2;
+----+-------------+-------+-------+---------------+--------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------+---------+------+------+--------------------------+
| 1 | SIMPLE | t1 | range | idx,u_idx2 | u_idx2 | 92 | NULL | 3 | Using where; Using index |
+----+-------------+-------+-------+---------------+--------+---------+------+------+--------------------------+
Impossible WHERE:查询语句总是false,不能查询出任何数据,相当于要求一个人既是男性又是女性…
explain select * from t1 where other_column = 'A' and other_column= 'B';
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
延伸案例(执行顺序分析)
explain select d1.other_column,(select id from t3) d2 from (select id,other_column from t1 where other_column='') d1 union (select other_column,id from t2);
+------+--------------+------------+--------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+------------+--------+---------------+---------+---------+------+------+-------------+
| 1 | PRIMARY | <derived3> | system | NULL | NULL | NULL | NULL | 1 | |
| 3 | DERIVED | t1 | ref | idx,u_idx2 | idx | 92 | | 1 | Using where |
| 2 | SUBQUERY | t3 | index | NULL | PRIMARY | 4 | NULL | 5 | Using index |
| 4 | UNION | t2 | ALL | NULL | NULL | NULL | NULL | 5 | |
| NULL | UNION RESULT | <union1,4> | ALL | NULL | NULL | NULL | NULL | NULL | |
+------+--------------+------------+--------+---------------+---------+---------+------+------+-------------+
第一行(执行顺序4):id列为1,表示union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中的3表示该查询衍生自第三个select查询,即id为3的select。select d1.other_column…
第二行(执行顺序2):id列为3,是整个查询中的第三个select的一部分。因查询包含在from中,所以derived。select id,other_column from t1 where other_column=’’
第三行(执行顺序3):selct列表中的子查询select_type为subquery,为整个查询中的第二个select。select id from t3
第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行。select other_column,id from t2
第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的<union1,4>表示用第一个和第四个select的结果进行union操作。两个结果union操作