Mysql-Explain(八):输出列-extra

简介

extra 包含其explain字段不适合显示但又十分重要的额外信息 using filesort 使用文件内排序
using tmporary 使用临时表保存中间结果,常见于排序order by和分组group by
Using index 表示复盖索引即可满足查询要求,因而无需再回表查询
Using index for group by 读取和分组都使用了覆盖索引
Using where Server层对存储引擎层返回的数据做where条件过滤
impossiable where where的值总fasle,不能获取任何记录
Using join buffer 联表查询时使用的缓存策略,有Block Nested-Loop Join和Batched Key Access两种策略
Select tables optimized away 在没有group by子句的情况下,基于索引优化的MAX/MIN操作,或者基于MyISAM存储引擎优化的COUNT(*)操作,不必等到执行阶段再进行计算,在查询计划生成阶段既可以完成优化
Distinct 优化Distinct操作,在找到匹配的第一行记录后,立马停止查找同样的值

演示

  • using filesort:使用文件内排序

    mysql> explain select * from student where school_id = 1 order by name;
    +----+-------------+---------+------------+------+---------------------+---------------------+---------+-------+------+----------+----------------+
    | id | select_type | table   | partitions | type | possible_keys       | key                 | key_len | ref   | rows | filtered | Extra          |
    +----+-------------+---------+------------+------+---------------------+---------------------+---------+-------+------+----------+----------------+
    |  1 | SIMPLE      | student | NULL       | ref  | ik_schoolId_majorId | ik_schoolId_majorId | 5       | const | 1960 |   100.00 | Using filesort |
    +----+-------------+---------+------------+------+---------------------+---------------------+---------+-------+------+----------+----------------+
    1 row in set, 1 warning (0.00 sec)
    

    ik_schoolId_majorId只能用于查询,无帮助name字段完成排序,所以MySQL需要借助临时文件来完成排序。Using filesort一般在是我们不太愿意看见的,我们写Sql语句的时候要尽量避免出现文件内排序。

    mysql> explain select count(*) as count,school_id from student group by school_id order by count;
    +----+-------------+---------+------------+-------+---------------------+---------------------+---------+------+---------+----------+----------------------------------------------+
    | id | select_type | table   | partitions | type  | possible_keys       | key                 | key_len | ref  | rows    | filtered | Extra                                        |
    +----+-------------+---------+------------+-------+---------------------+---------------------+---------+------+---------+----------+----------------------------------------------+
    |  1 | SIMPLE      | student | NULL       | index | ik_schoolId_majorId | ik_schoolId_majorId | 10      | NULL | 1994142 |   100.00 | Using index; Using temporary; Using filesort |
    +----+-------------+---------+------------+-------+---------------------+---------------------+---------+------+---------+----------+----------------------------------------------+
    1 row in set, 1 warning (0.00 sec)
    

    当然我们无法说有Using filesort的Sql语句就一定有问题,而且有些时候也无法完全消除Using filesort。
    例如上面例子中的语句,需要对分组统计的数量进行排序,这个就无法通过索引来完成排序,必须通过文件内排序来完成。如果Using filesort真的就那么十恶不赦的话,Mysql早就将它清除了。但是我们还是必须要将文件类排序的消耗降到最低,尽量多用索引,尽量减少需要排序的行数。

  • using tmporary:使用临时表保存中间结果,常见于排序order by和分组group by

    mysql> explain select count(*) as count,school_id from student group by school_id order by count;
    +----+-------------+---------+------------+-------+---------------------+---------------------+---------+------+---------+----------+----------------------------------------------+
    | id | select_type | table   | partitions | type  | possible_keys       | key                 | key_len | ref  | rows    | filtered | Extra                                        |
    +----+-------------+---------+------------+-------+---------------------+---------------------+---------+------+---------+----------+----------------------------------------------+
    |  1 | SIMPLE      | student | NULL       | index | ik_schoolId_majorId | ik_schoolId_majorId | 10      | NULL | 1994142 |   100.00 | Using index; Using temporary; Using filesort |
    +----+-------------+---------+------------+-------+---------------------+---------------------+---------+------+---------+----------+----------------------------------------------+
    1 row in set, 1 warning (0.00 sec)
    

    还是上面的结果,MySQL使用临时表保持了分组统计的结果,再对结果进行了排序。using tmporary和Using filesort一样不是一个我们乐意见到的东西,因此和对待Using filesort一样,可以避免就尽量避免,如果无法避免,就尽量将临时表的记录行数降到最少,减少其开销。而且实在不行了,有时候可以通过程序层面去消除它,不一定非要纠结在MySQL语句上,比如把一个很长的语句分成几个。

  • Using index:表示复盖索引即可满足查询要求,因而无需再回表查询。

    mysql> explain select id,school_id,major_id from student;
    +----+-------------+---------+------------+-------+---------------+---------------------+---------+------+---------+----------+-------------+
    | id | select_type | table   | partitions | type  | possible_keys | key                 | key_len | ref  | rows    | filtered | Extra       |
    +----+-------------+---------+------------+-------+---------------+---------------------+---------+------+---------+----------+-------------+
    |  1 | SIMPLE      | student | NULL       | index | NULL          | ik_schoolId_majorId | 10      | NULL | 1994142 |   100.00 | Using index |
    +----+-------------+---------+------------+-------+---------------+---------------------+---------+------+---------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
  • Using where:表示MySQL将对存储引擎层提取的结果进行过滤,它表示的是Server层对存储引擎层返回的数据所做的过滤。

    mysql> explain select * from student where school_id = 1 and name like '%N%';
    +----+-------------+---------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
    | id | select_type | table   | partitions | type | possible_keys       | key                 | key_len | ref   | rows | filtered | Extra       |
    +----+-------------+---------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
    |  1 | SIMPLE      | student | NULL       | ref  | ik_schoolId_majorId | ik_schoolId_majorId | 5       | const | 1960 |    11.11 | Using where |
    +----+-------------+---------+------------+------+---------------------+---------------------+---------+-------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    

    索引ik_schoolId_majorId在存储引擎层仅仅能完成shcool_id的条件读取,然后存储引擎返回结果到Server层,然后Server层再对数据进行name条件的筛选。

  • Using index condition:使用了Index Condition Pushdown (IPC) ,这是Mysql 5.6开始支持的一种根据索引进行查询的优化方式。其优化支持range、ref、eq_ref、ref_or_null类型的查询,当前支持MyISAM和InnoDB存储引擎。

    mysql> explain select * from student where school_id < 5 and major_id < 5;
    +----+-------------+---------+------------+-------+--------------------------------+---------------------+---------+------+-------+----------+-----------------------+
    | id | select_type | table   | partitions | type  | possible_keys                  | key                 | key_len | ref  | rows  | filtered | Extra                 |
    +----+-------------+---------+------------+-------+--------------------------------+---------------------+---------+------+-------+----------+-----------------------+
    |  1 | SIMPLE      | student | NULL       | range | ik_schoolId_majorId,ik_majorId | ik_schoolId_majorId | 5       | NULL | 19444 |     1.90 | Using index condition |
    +----+-------------+---------+------------+-------+--------------------------------+---------------------+---------+------+-------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    

    根据Mysql的最左原则,“where school_id < 5 and major_id < 5”,索引k_schoolId_majorId 只有第一个字段school_id起作用,按照上面Using where的说法,应该是存储层返回符合“ school_id < 5 ”条件的数据到server层,然后再对“major_id < 5”条件进行匹配,然而extra并没有出现Using where。

    这是因为Mysql使用了IPC,在上面的例子中虽然无法直接通过索引读取所需的记录,但是where条件都在ik_schoolId_majorId中,因此Mysql在读取符合“ school_id < 5 ”条件的索引结果后,并没有利用索引结果直接去读取源表,然后返回server层进行第二次的筛选。而是直接利用索引树再对上面索引结果进行一次筛选,然后再去读取源表数据,从而减少了对源表读取消耗。

    在上面Using where的例子中无法使用IPC,那是因为name条件不是索引ik_schoolId_majorId所包含的条件,无法利用索引树进行筛选。

  • Using join buffer:联表查询缓存。
    Block Nested-Loop Join算法:将外层循环的行/结果集存入join buffer, 内层循环的每一行与整个buffer中的记录做比较,从而减少内层循环的次数。优化器管理参数optimizer_switch中中的block_nested_loop参数控制着BNL是否被用于优化器。默认条件下是开启,若果设置为off,优化器在选择 join方式的时候会选择NLJ(Nested Loop Join)算法。
    Batched Key Access原理:对于多表join语句,当MySQL使用索引访问第二个join表的时候,使用一个join buffer来收集第一个操作对象生成的相关列值。BKA构建好key后,批量传给引擎层做索引查找。key是通过MRR接口提交给引擎的(mrr目的是较正顺序)MRR使得查询更有效率,要使用BKA,必须调整系统参数optimizer_switch的值,batched_key_access设置为on,因为BKA使用了MRR,因此也要打开MRR。

  • impossiable where:where的值总fasle,不能获取任何记录

    mysql> explain select * from student where name = 'NnbtPQLjMf' and name = 'aa';
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    | 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.00 sec)
    
  • Select tables optimized away:在没有group by子句的情况下,基于索引优化的MAX/MIN操作,或者基于MyISAM存储引擎优化的COUNT(*)操作,不必等到执行阶段再进行计算,在查询计划生成阶段既可以完成优化

    mysql> explain select max(school_id) from student;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    | 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 | Select tables optimized away |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    1 row in set, 1 warning (0.02 sec)
    
  • Distinct:优化Distinct操作,在找到匹配的第一行记录后,立马停止查找同样的值

    mysql> explain select distinct major_id from student left join school on school.id = student.school_id where major_id = 1;
    +----+-------------+---------+------------+--------+--------------------------------+------------+---------+------------------------+------+----------+-----------------------+
    | id | select_type | table   | partitions | type   | possible_keys                  | key        | key_len | ref                    | rows | filtered | Extra                 |
    +----+-------------+---------+------------+--------+--------------------------------+------------+---------+------------------------+------+----------+-----------------------+
    |  1 | SIMPLE      | student | NULL       | ref    | ik_schoolId_majorId,ik_majorId | ik_majorId | 5       | const                  | 4027 |   100.00 | Using temporary       |
    |  1 | SIMPLE      | school  | NULL       | eq_ref | PRIMARY                        | PRIMARY    | 4       | mydb.student.school_id |    1 |   100.00 | Using index; Distinct |
    +----+-------------+---------+------------+--------+--------------------------------+------------+---------+------------------------+------+----------+-----------------------+
    2 rows in set, 1 warning (0.00 sec)
    

    查询所有有专业1(major_id=1)的学校Id,由于有多个学生是同一个学校同一个专业的,但是对于查询而言只需要查询到一条记录即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章