MySQL的的执行计划解释命令explain有个extended选项,这个选项在MySQL早期的版本中会产生额外的信息,这些额外的信息在explain的输入中不会显示,需要在运行explain之后使用show warnings命令查看。在5.7以上版本中,extended已经成为explain的默认选项,这也是每次执行explain命令后,总显示有一条告警的原因,extended命令选项虽然还能用,已经没有什么实际意义,纯粹是为了兼容以前的版本,在后面的版本中可能会被取消。
1 sql语句
要解析的sql语句看起来稍微复杂一点,这样写的目的是尽可能的展示MySQL执行计划的不同内容,查询的是sakila数据库中管理员id为2 的仓库在所有国家用户的id,地址id,first_name,lastname,连接了sakila数据库的customer,addres,city三个表,对city的查询加了子查询,这里有意增加了复杂度,对子查询使用了联合,联合的是不是“Yemen”的国家和不是"Vietnam"的国家,联合起来就是所有的国家。对customer查询则简单使用了子查询 。
mysql> explain select a.customer_id, a.address_id, a.first_name, a.last_namefrom customer a inner join address b on a.address_id=b.address_id inner join city c on b.city_id=c.city_idwhere c.country_idin(select country_id from country where country<>"Yemen"unionselect country_id from country where country<>"Vietnam")and a.store_idnotin(select store_id from store where manager_staff_id=2);
2 语句的执行计划
+----+--------------------+------------+--------+--------------------+---------------------+-----------------+| id | select_type |table| type | key | ref | Extra |+----+--------------------+------------+--------+--------------------+---------------------+-----------------+|1| PRIMARY | a | ALL |NULL|NULL| Using where||1| PRIMARY | b | eq_ref | PRIMARY | sakila.a.address_id|NULL||1| PRIMARY | c | eq_ref | PRIMARY | sakila.b.city_id| Using where||4| SUBQUERY | store | const | idx_unique_manager | const | Using index ||2| DEPENDENT SUBQUERY | country | eq_ref | PRIMARY | func | Using where||3| DEPENDENT UNION| country | eq_ref | PRIMARY | func | Using where||NULL|UNION RESULT |<union2,3>| ALL |NULL|NULL| Using temporary |+----+--------------------+------------+--------+--------------------+---------------------+-----------------+
select_type为PRIMARY的查询是最外层的查询,如果是子查询的时候,最外层的查询的类型是PRIMARY,外连接内连接最左边的查询其类型依旧是simple,从下面这个简单的例子可以看出来。
mysql> explain select a.city,b.countryfrom city a left join country b on a.country_id=b.country_id;+----+-------------+-------+--------+---------+---------------------+------+-------+| id | select_type |table| type | key | ref | rows | Extra |+----+-------------+-------+--------+---------+---------------------+------+-------+|1| SIMPLE | a | ALL |NULL|NULL|600|NULL||1| SIMPLE | b | eq_ref | PRIMARY | sakila.a.country_id|1|NULL|+----+-------------+-------+--------+---------+---------------------+------+-------+2 rows inset,1 warning (0.00 sec)
DEPENDENT SUBQUERY和DEPENDENT UNION的执行依赖外层查询执行的结果,在实际的sql编写中是应该尽可能避免的。
3 show warnings
show warnings只对select语句有效,对update、delete和insert是无效的,命令显示的是在select语句中优化器是怎样标准化表名和列名,这里显示的sql语句是经过重写和应用优化规则后看起来的样子,还有关于优化器过程的其它信息。
show warnings的输出里面有很多特殊的标记,这给阅读和理解造成了不小的麻烦,输入的格式也不是很友好,可读性较差,下面显示的输出是经过格式化了,读起来更容易一点。
mysql> show warnings\G;***************************1. row *************************** Level: Note Code:1003 Message:/* select#1 */select `sakila`.`a`.`customer_id` AS `customer_id`, `sakila`.`a`.`address_id` AS `address_id`, `sakila`.`a`.`first_name` AS `first_name`,`sakila`.`a`.`last_name` AS `last_name` from `sakila`.`customer` `a` join `sakila`.`address` `b` join `sakila`.`city` `c` where((`sakila`.`b`.`address_id` = `sakila`.`a`.`address_id`)and(`sakila`.`c`.`city_id` = `sakila`.`b`.`city_id`)and<in_optimizer>( `sakila`.`c`.`country_id`,<exists>(/* select#2 */select1from `sakila`.`country` where((`sakila`.`country`.`country` <>'Yemen')and(<cache>(`sakila`.`c`.`country_id`)= `sakila`.`country`.`country_id`))union/* select#3 */select1from `sakila`.`country` where((`sakila`.`country`.`country` <>'Vietnam')and(<cache>(`sakila`.`c`.`country_id`)= `sakila`.`country`.`country_id`))))and(not(<in_optimizer>(`sakila`.`a`.`store_id`,`sakila`.`a`.`store_id` in(<materialize>(/* select#4 */select'2'from `sakila`.`store` where1),<primary_index_lookup>(`sakila`.`a`.`store_id` in<temporary table>on<auto_key>where((`sakila`.`a`.`store_id` = `materialized-subquery`.`store_id`))))))))1 row inset(0.00 sec) ERROR: No query specified
show warnings的输出里面有很多特殊标记,其中几个常见的标记的含义如下面所示:
<auto_key>是为临时表自动创建的键(索引),
<cache>表达式被执行了一次,获得的值存储到了内存中以供后来使用。如果表达式的执行结果是多个值,可能会创建一个临时表,那么这里看到的是<temporary table> 。
<exists> 子查询匹配被转换成exists匹配,子查询需要转换,然后可以和exists一起使用的。
<in_optimizer>优化器对象,对用户来说没有意义。
<materialize>使用了子查询物化
<primary_index_lookup> 使用主键来查询符合的行。
知道了上面几个特殊标记的含义,就可以对show warnings的结果做出解释来了。
/* select#1 */对应与执行计划中id为1的操作,一共有三个操作,执行的顺序是按从上到下依次执行的,这是一个三表连接操作,连接的顺序是a,b,c .后面的where条件里先出现的是这三个表 连接的join键,后面的两个部分分别对应sql语句中的其它两个where条件,<in_optimizer>表示后面的对象是优化器对象,可以不用关注。
第一个where条件的<exists>表明后面的子查询被优化器转换成了exist表达式,/* select#2 */和/* select#3 */分别对应执行计划中id为2和3的查询,id为2的是依赖子查询,id为3 的是依赖union,它们依赖外层的country表传来的country_id,首次访问country_id后,其值被缓存起来以供后续使用。
第二个where条件中的子查询使用了物化优化,/* select#4 */对应执行计划中的id为4的操作,从<primary_index_lookup>可以看出这个查询使用主键查询物化的临时表的行,使用的主键是MySQL为物化临时表自动创建的<auto_key>。
从show warnings的结果中把所有的特殊标记去掉,就是经过优化器改写和转换后的sql语句。可以看出,MySQL优化器把第一个子查询转换成了exists表达式,对第二个子查询进行了物化优化。