透彻研究通过explain命令得到的SQL执行计划(二)

继续讲解不同SQL语句的执行计划长什么样子,来一起看一个包含子查询的SQL语句的执行计划:

EXPLAIN SELECT * FROM t1 WHERE x1 IN (SELECT x1 FROM t2) OR x3 = 'xxxx';

这个SQL就稍微有一点点的复杂了,因为主SELECT语句的WHERE筛选条件是依赖于一个子查询的,而且除此之外还有一个自己的WHERE筛选条件,那么它的执行计划长什么样子呢?

+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
 
| id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
|  1 | PRIMARY     | t1    | NULL       | ALL   | index_x3      | NULL     | NULL    | NULL | 3457 |   100.00 | Using where |
 
|  2 | SUBQUERY    | t2    | NULL       | index | index_x1      | index_x1 | 507     | NULL | 4687 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+

这个执行计划值得我们好好分析一下,首先,第一条执行计划的id是1,第二条执行计划的id是2,这是为什么?因为这个SQL里有两个SELECT,主查询SELECT的执行计划的id就是1,子查询SELECT的执行计划的id就是2。

其次,第一条执行计划里,select_type是PRIMARY,不是SIMPLE了,说明第一个执行计划的查询类型是主查询的意思,对主查询而言,它有一个where条件是x3=‘xxx’,所以它的possible_keys里包含了index_x3,就是x3字段的索引,但是它的key实际是NULL,而且type是ALL,所以说它最后没选择用x3字段的索引,而是选择了全表扫描。

这是为什么呢?其实很简单,可能它通过成本分析发现,使用x3字段的索引扫描xxx这个值,几乎就跟全表扫描差不多,可能x3这个字段的值几乎都是xxx,所以最后就选择还不如直接全表扫描呢。

接着第二条执行计划,它的select_type是SUBQUERY,也就是子查询,子查询针对的是t2这个表,当然子查询本身就是一个全表查询,但是对主查询而言,会使用x1 in 这个筛选条件,它这里type是index,说明使用了扫描index_x1这个x1字段的二级索引的方式,直接扫描x1字段的二级索引,来跟子查询的结果集做比对。

EXPLAIN SELECT * FROM t1  UNION SELECT * FROM t2

那么它的执行计划是什么样的呢?

+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
 
| id | select_type  | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
 
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
 
|  1 | PRIMARY      | t1         | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 3457 |   100.00 | NULL            |
 
|  2 | UNION        | t2         | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4687 |   100.00 | NULL            |
 
| NULL | UNION RESULT | <union1,2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | NULL |     NULL | Using temporary |
 
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+

这个执行计划的第一条和第二条很好理解对吧?两个SELECT字句对应两个id,就是分别从t1表和t2表里进行全表扫描罢了。

接着第三条执行计划是什么呢?其实union字句默认的作用是把两个结果集合并起来还会进行去重,所以第三条执行计划干的是个去重的活儿。所以上面它的table是<union 1,2>,这就是一个临时表的表名,而且你看它的extra里,有一个using temporary,也就是使用临时表的意思,它就是把结果集放到临时表里进行去重的,就这么个意思。当然,如果你用的是union all,那么就不会进行去重了。

接着来看看这个select_type,select_type之前似乎看到过几种,有什么SIMPLE的,还有primary和subquery的,那么这些select_type都是什么意思?除此之外,还有哪几种select_type呢?

首先要告诉大家的是,一般如果单表查询或者是多表连接查询,其实它们的select_type都是SIMPLE,这个之前也都看到过了,意思就是简单的查询罢了。然后如果是union语句的话,就类似于select * from t1 union select * from t2,那么会对应两条执行计划,第一条执行计划是针对t1表的,select_type是PRIMARY,第二条执行计划是针对t2表的,select_type是UNION,这就是在出现union语句的时候,它们就不一样了。

在使用union语句的时候,会有第三条执行计划,这个第三条执行计划意思是针对两个查询的结果依托一个临时表进行去重,这个第三条执行计划的select_type就是union_result。

另外,之前还看到过,如果是在SQL里有子查询,类似于select * from t1 where x1 in (select x1 ffrom t2) or x3=‘xxx’,此时其实会有两条执行计划,第一条执行计划的select_type是PRIMARY,第二条执行计划的select_type是SUBQUERY,这个之前也看到过了。

那么现在来看一个稍微复杂一点的SQL语句:

EXPLAIN SELECT * FROM t1 WHERE x1 IN (SELECT x1 FROM t2 WHERE x1 = 'xxx' UNION SELECT x1 FROM t1 WHERE x1 = 'xxx');

这个SQL语句就稍微有点复杂了,因为它有一个外层查询,还有一个内层子查询,子查询里还有两个SELECT语句进行union操作,那么来看看它的执行计划会是什么样的呢?

+----+--------------------+------------+------------+------+---------------+----------+---------+-------+------+----------+--------------------------+
 
| id | select_type        | table      | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra |
 
+----+--------------------+------------+------------+------+---------------+----------+---------+-------+------+----------+--------------------------+
 
|  1 | PRIMARY            | t1         | NULL       | ALL  | NULL          | NULL     | NULL    | NULL  | 3467 |   100.00 | Using where              |
 
|  2 | DEPENDENT SUBQUERY | t2         | NULL       | ref  | index_x1      | index_x1 | 899     | const |   59 |   100.00 | Using where; Using index |
 
|  3 | DEPENDENT UNION    | t1         | NULL       | ref  | index_x1      | index_x1 | 899     | const |    45 |   100.00 | Using where; Using index |
 
| NULL | UNION RESULT       | <union2,3> | NULL       | ALL  | NULL          | NULL     | NULL    | NULL  | NULL |     NULL | Using temporary |
 
+----+--------------------+------------+------------+------+---------------+----------+---------+-------+------+----------+--------------------------+

第一个执行计划一看就是针对t1表查询的那个外层循环,select_type就是PRIMARY,因为这里涉及到了子查询,所以外层查询的select_type一定是PRIMARY了。然后第二个执行计划是子查询里针对t2表的那个查询语句,它的select_type是DEPENDENT SUBQUERY,第三个执行计划是子查询里针对t1表的另外一个查询语句,select_type是DEPENDENT UNION,因为第三个执行计划是在执行union后的查询,第四个执行计划的select_type是UNION RESULT,因为在执行子查询里两个结果集的合并以及去重。

现在再来看一个更加复杂一点的SQL语句:

EXPLAIN SELECT * FROM (SELECT x1, count(*) as cnt FROM t1 GROUP BY x1) AS _t1 where cnt > 10;

这个SQL可有点麻烦了,FROM子句后跟了一个子查询,在子查询里是根据x1字段进行分组然后进行count聚合操作,也就是统计出来x1这个字段每个值的个数,然后在外层则是针对这个内层查询的结果集进行查询通过where条件来进行过滤,看看它的执行计划:

+----+-------------+------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
 
| id | select_type | table      | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra |
 
+----+-------------+------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
 
|  1 | PRIMARY     | <derived2> | NULL       | ALL   | NULL          | NULL     | NULL    | NULL | 3468 |    33.33 | Using where |
 
|  2 | DERIVED     | t1         | NULL       | index | index_x1      | index_x1 | 899     | NULL | 3568 |   100.00 | Using index |
 
+----+-------------+------------+------------+-------+---------------+----------+---------+------+------+----------+-------------+

上面的执行计划里,其实应该先看第二条执行计划,它说的是子查询里的那个语句的执行计划,它的select_type是derived,意思就是说,针对子查询执行后的结果集会物化为一个内部临时表,然后外层查询是针对这个临时的物化表执行的。

它这里执行分组聚合的时候,是使用的index_x1这个索引来进行的,type是index,意思就是直接扫描偶了index_x1这个索引树的所有叶子节点,把x1相同值的个数都统计出来就可以了。然后外层查询是第一个执行计划,select_type是PRIMARY,针对的table是,就是一个子查询结果集物化形成的临时表,它是直接针对这个物化临时表进行了全表扫描根据where条件进行筛选的

上一篇:服务化-mysql-05 索引优化及Explain实践详解


下一篇:Explain 详解