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,由於有多個學生是同一個學校同一個專業的,但是對於查詢而言只需要查詢到一條記錄即可。

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