前面说了有type,simple表示普通查询或者连接查询,primary代表union最左边的select,union result代表union查询的临时表去重,所以union all没有去重功能,subquery代表in的子查询物化表的情况下才会出现,dependent subquery代表相关子查询,dependent union代表相关union查询,还有driverd子查询,from后面的,也需要物化,还有物化后转连接查询,这些都能看到mysql优化器是采用哪种查询方式。
Id代表select,几个select就有几个查询,如果转链接了,就只有一个id。
Type代表存储引擎查询的方式,system代表精确查询并且只有一条记录,const,唯一索引或者主键,ref,二级索引查询,ref_or_null,range,index代表联合索引但没有触发,range和ref,最后还要all。
Possible key代表可能用到的索引。
Key实际用到的索引
Ref代表后面的过滤条件可能用到的方式,他可能是个函数,也可能是驱动表的参数。
Rows代表查询的数据,全表代表所有,索引则代表所有满足的数据。
Filtered代表数据多少满足,和rows组合可以算出去驱动表的扇出值。
key&key_len&ref&filtered(4)—mysql执行计划(五十)
Extra
顾名思义,这列就存储的是额外信息,我们可以通过额外信息准确理解mysql到底执行查询语句。
mysql> EXPLAIN SELECT 1; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ 1 row in set, 1 warning (0.00 sec) //当我们的sql语句没有表时候,extra显示的是no tables used。 mysql> EXPLAIN SELECT * FROM s1 WHERE 1 != 1; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+ 1 row in set, 1 warning (0.01 sec) //当过滤条件后面永远显示false,就会出现Impossible where mysql> EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg'; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No matching min/max row | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------------------+ 1 row in set, 1 warning (0.00 sec) //当查询列表有max或者min,但没有查到数据时候,会显示:no matching min/max row mysql> EXPLAIN SELECT key1 FROM s1 WHERE key1 = 'a'; +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 8 | 100.00 | Using index | +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) //当我们在使用索引覆盖的情况下,当我们只需要用到索引,而不需要回表操作,则显示 using index。 SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a'; mysql> EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%b'; +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+ | 1 | SIMPLE | s1 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 266 | 100.00 | Using index condition | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+ 1 row in set, 1 warning (0.01 sec) //有些虽然用了索引列,但不能使用到索引 //其中key1>'z'可以使用到索引,但后面的like不可以使用索引, //传统访问应该先索引在索引b+树查询满足z的条件,然后数据回表查询过滤满足like '%a'的数据。 //但其实可以在索引b+树查询满足z的条件后,继续在b+树过滤索引满足key1 like '%a'的索引,因为key1 //也在索引b+树,这样就减少回表的开销。而且因为回表是随机I/O,这边是顺序I/O,效率也会快很多。 //mysql吧这种就称呼 索引条件下推,using index condition mysql> EXPLAIN SELECT * FROM s1 WHERE common_field = 'a'; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 10.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.01 sec) //当我们全表扫描的时候,显示的是using where mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a'; +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | const | 8 | 10.00 | Using where | +----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) //当我们使用索引访问,但还有其他搜索条件还有common_Filed所以还是显示suing where mysql> EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL | | 1 | SIMPLE | s2 | NULL | ALL | NULL | NULL | NULL | NULL | 9954 | 10.00 | Using where; Using join buffer (Block Nested Loop) | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ 2 rows in set, 1 warning (0.03 sec) //前面说过基于块的嵌套法,当被驱动表不能通过索引访问,这时候会有一个join buffer,来加快查询的速度,因为这个太多的话 //内存里不够存放足够多的数据,只能不断通过驱动表查询的结果,一条条从磁盘访问驱动表, //这时候就把被驱动表的数据放在join buffer里,直接在内存中处理是否满足,就不需要每次都从磁盘查询数据。 //所以上面可以看到,因为不能使用索引扫描,所以退而求其次,用join buffer, //又因为s2.common_field = 常数,所以这里又有一个using where mysql> EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL; +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | NULL | | 1 | SIMPLE | s2 | NULL | ref | idx_key1 | idx_key1 | 303 | xiaohaizi.s1.key1 | 1 | 10.00 | Using where; Not exists | +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------------------+ 2 rows in set, 1 warning (0.00 sec) //当我们在外连接,如果where条件包含被驱动表某个列等于null,但当前列有不允许为null,这时候就会显示not exists mysql> EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND key3 = 'a'; +----+-------------+-------+------------+-------------+-------------------+-------------------+---------+------+------+----------+-------------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------------+-------------------+-------------------+---------+------+------+----------+-------------------------------------------------+ | 1 | SIMPLE | s1 | NULL | index_merge | idx_key1,idx_key3 | idx_key3,idx_key1 | 303,303 | NULL | 1 | 100.00 | Using intersect(idx_key3,idx_key1); Using where | +----+-------------+-------+------------+-------------+-------------------+-------------------+---------+------+------+----------+-------------------------------------------------+ 1 row in set, 1 warning (0.01 sec) //前面说过如果单独的索引,会使用合并索引,上面就显示用的using_intersect(idx_key3,idx_key1) //如果是union则就用的是using_union合并 mysql> EXPLAIN SELECT * FROM s1 LIMIT 0; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Zero limit | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------+ 1 row in set, 1 warning (0.00 sec) //如果分页是0,则直接会显示zero limit mysql> EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10; +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------+ | 1 | SIMPLE | s1 | NULL | index | NULL | idx_key1 | 303 | NULL | 10 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------+ 1 row in set, 1 warning (0.03 sec mysql> EXPLAIN SELECT * FROM s1 ORDER BY common_field LIMIT 10; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | Using filesort | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ 1 row in set, 1 warning (0.00 sec) //前面那个显示的null,对索引进行排序了 //但有些时候无法使用索引排序,这时候就需要从磁盘排序或者内存排序,这些都叫file sort //如果用文件排序则就会显示using filesort mysql> EXPLAIN SELECT DISTINCT common_field FROM s1; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | Using temporary | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+ 1 row in set, 1 warning (0.00 sec) //有的时候mysql会使用临时表进行去重或者排序,比如我们在distinct,group by,union等子句的查询过程, //这时候如果不能利用索引查询,这时候会建立内部临时表,这时候就会显示 Using temporary mysql> EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | Using temporary; Using filesort | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field ORDER BY NULL; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+ | 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 9688 | 100.00 | Using temporary | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+ 1 row in set, 1 warning (0.00 sec) EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field ORDER BY common_field; //上面的显示不光有using temporary 还有using filesort,也就是说不光临时表,还排序了。 //这是因为mysql默认在group by之后默认会order by,不想排序必须显示写 order by null mysql> EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1; +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+ | 1 | SIMPLE | s1 | NULL | index | idx_key1 | idx_key1 | 303 | NULL | 9688 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) //另外建立临时表代价很大,能用索引 就用索引,如果用到索引group by 就会显示using index,并且索引是排序好的。 mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2 WHERE common_field = 'a'); +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+ | 1 | SIMPLE | s2 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 9954 | 10.00 | Using where; Start temporary | | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | xiaohaizi.s2.key3 | 1 | 100.00 | End temporary | +----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+------------------------------+ 2 rows in set, 1 warning (0.00 sec) //当我们查询优化器吧子查询in转为semi-join的时候,有很多执行策略,其实一种也是建立临时表实现为外层查询进去重操作 //这时候驱动表start temporary 被驱动表将会显示end temporary mysql> EXPLAIN SELECT * FROM s1 WHERE key3 IN (SELECT key1 FROM s2 WHERE key1 > 'z'); +----+-------------+-------+------------+-------+---------------+----------+---------+-------------------+------+----------+-------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+----------+---------+-------------------+------+----------+-------------------------------------+ | 1 | SIMPLE | s2 | NULL | range | idx_key1 | idx_key1 | 303 | NULL | 270 | 100.00 | Using where; Using index; LooseScan | | 1 | SIMPLE | s1 | NULL | ref | idx_key3 | idx_key3 | 303 | xiaohaizi.s2.key1 | 1 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------+----------+---------+-------------------+------+----------+-------------------------------------+ 2 rows in set, 1 warning (0.01 sec) //内连接还会显示looseScan mysql> EXPLAIN SELECT * FROM s1 WHERE common_field IN (SELECT key1 FROM s2 where s1.key3 = s2.key3); +----+-------------+-------+------------+------+-------------------+----------+---------+-------------------+------+----------+-----------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+-------------------+----------+---------+-------------------+------+----------+-----------------------------+ | 1 | SIMPLE | s1 | NULL | ALL | idx_key3 | NULL | NULL | NULL | 9688 | 100.00 | Using where | | 1 | SIMPLE | s2 | NULL | ref | idx_key1,idx_key3 | idx_key3 | 303 | xiaohaizi.s1.key3 | 1 | 4.87 | Using where; FirstMatch(s1) | +----+-------------+-------+------------+------+-------------------+----------+---------+-------------------+------+----------+-----------------------------+ 2 rows in set, 2 warnings (0.00 sec) //内连接还会显示firstMatch