Mysql 執行計劃

不論是開發、運維仍是實施等崗位的同窗,對於本身所接觸、所編寫的各類SQL語句,都應該可以進行調優,從而使本身能寫出更優解的SQL語句,我的以爲更是一種必備技能。mysql

  • 如何查看各個sql語句的執行計劃,是本篇文章的主題。
  • Mysql查看執行計劃通常是經過 explain + sql。
  • 本文假設讀者已經掌握mysql相關的索引知識。

先提供例子中涉及到表的建表、建索引語句。算法

-- 用戶表
create table t_user(
id int primary key,
loginname varchar(100),  
name varchar(100),
age int,
sex char(1),
dep_id int,  
address varchar(100)
);

--部門表
create table t_dep(
id int primary key,
name varchar(100)
);

--建立普通索引
mysql> alter table t_user add index idx_dep_id(dep_id);
--建立惟一索引
mysql> alter table t_user add unique index uk_loginname(loginname);
--建立組合索引
mysql> alter table t_user add index idx_name_age_sex(name,age,sex);

explain出來的信息有12列,分別是:sql

id、select_type 、table 、partitions 、type、possible_keys、key 、key_len、ref 、rows、filtered、Extra

接下來會對各個字段作對應的說明,並列舉對應的例子。服務器

 

  •  id

    每一個select語句都會自動分配一個惟一的標識符運維

  • select_type

    表示SELECT的類型,常見的取值有SIMPLE(簡單表,即不使用錶鏈接查詢)、PRIMARY(主查詢,即外層的查詢)、UNION(UNION中的第二個或者後面的查詢語句)、SUBQUERY(子查詢中的第一個SELECT)等性能

    1. SIMPLE
      簡單表,即不使用錶鏈接查詢
      mysql> explain select * from t_user;
      +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
      | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
      +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
      |  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | NULL  |
      +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
      1 row in set, 1 warning (0.00 sec)
    2. PRIMARY
      主查詢,即外層的查詢。一個須要union或者子查詢的select,位於最外層的單位查詢的select_type。
      mysql> explain select (select name from t_user) from t_user;
      +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
      | id | select_type | table  | partitions | type  | possible_keys | key              | key_len | ref  | rows | filtered | Extra       |
      +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
      |  1 | PRIMARY     | t_user | NULL       | index | NULL          | idx_dep_id       | 5       | NULL |    6 |   100.00 | Using index |
      |  2 | SUBQUERY    | t_user | NULL       | index | NULL          | idx_name_age_sex | 312     | NULL |    6 |   100.00 | Using index |
      +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
      2 rows in set, 1 warning (0.00 sec)
    3. UNION    
      UNION中的第二個或者後面的查詢語句,union鏈接的兩個查詢語句,第一個是PRIMARY,除了第一個表外,第二個之後的表select_type都是union
      mysql> explain select name from t_user union select name from t_user2;
      +----+--------------+------------+------------+-------+---------------+------------------+---------+------+---------+----------+-----------------+
      | id | select_type  | table      | partitions | type  | possible_keys | key              | key_len | ref  | rows    | filtered | Extra           |
      +----+--------------+------------+------------+-------+---------------+------------------+---------+------+---------+----------+-----------------+
      |  1 | PRIMARY      | t_user     | NULL       | index | NULL          | idx_name_age_sex | 312     | NULL |       6 |   100.00 | Using index     |
      |  2 | UNION        | t_user2    | NULL       | ALL   | NULL          | NULL             | NULL    | NULL | 9756827 |   100.00 | NULL            |
      | NULL | UNION RESULT | <union1,2> | NULL       | ALL   | NULL          | NULL             | NULL    | NULL |    NULL |     NULL | Using temporary |
      +----+--------------+------------+------------+-------+---------------+------------------+---------+------+---------+----------+-----------------+
      3 rows in set, 1 warning (0.00 sec)
  • table

    輸出結果集的表優化

  • type

    表示MySQL在表中找到所需行的方式,或者叫訪問類型,常見類型以下:線程

    ALL < index < range < ref < eq_ref < const,systemserver

    從左到右,性能由差到最好blog

  1. ALL  
    全表掃描,MySQL遍歷全表來找到匹配的行
    mysql> explain select * from t_user;
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
  2. index 
    索引全掃描,MySQL遍歷整個索引來查詢匹配的行
    mysql> explain select name from t_user;
    +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type  | possible_keys | key              | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | t_user | NULL       | index | NULL          | idx_name_age_sex | 312     | NULL |    6 |   100.00 | Using index |
    +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
  3. range 
    索引範圍掃描,常見於<、<=、>、>=、between等操做符
    mysql> explain select * from t_user where dep_id > 10;
    +----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
    | id | select_type | table  | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | t_user | NULL       | range | idx_dep_id    | idx_dep_id | 5       | NULL |    1 |   100.00 | Using index condition |
    +----+-------------+--------+------------+-------+---------------+------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.01 sec)
  4. ref 
    使用非惟一索引掃描或惟一索引的前綴掃描,返回匹配某個單獨值得記錄行
    mysql> explain select * from t_user where dep_id = 10;
    +----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
    +----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | t_user | NULL       | ref  | idx_dep_id    | idx_dep_id | 5       | const |    1 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+---------------+------------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    

    索引idx_dep_id是非惟一索引,查詢條件爲等值查詢條件dep_id = 10,因此掃描索引的類型爲ref。ref 還常常出如今join操做中。

    mysql> explain select * from t_user a join t_dep b on a.name = b.name;
    +----+-------------+-------+------------+------+------------------+------------------+---------+------------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys    | key              | key_len | ref        | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+------------------+------------------+---------+------------+------+----------+-------------+
    |  1 | SIMPLE      | b     | NULL       | ALL  | NULL             | NULL             | NULL    | NULL       |    2 |   100.00 | Using where |
    |  1 | SIMPLE      | a     | NULL       | ref  | idx_name_age_sex | idx_name_age_sex | 303     | ssm.b.name |    1 |   100.00 | NULL        |
    +----+-------------+-------+------------+------+------------------+------------------+---------+------------+------+----------+-------------+
    2 rows in set, 1 warning (0.00 sec)
  5. eq_ref 
    相似ref,區別就在使用的索引是惟一索引,此類型一般出如今多表的join查詢;簡單來講,就是多表鏈接中使用primary key或者unique index 做爲關聯條件。
    mysql> explain select b.id from t_user a left join t_dep b on a.dep_id = b.id;
    +----+-------------+-------+------------+--------+---------------+------------+---------+--------------+------+----------+-------------+
    | id | select_type | table | partitions | type   | possible_keys | key        | key_len | ref          | rows | filtered | Extra       |
    +----+-------------+-------+------------+--------+---------------+------------+---------+--------------+------+----------+-------------+
    |  1 | SIMPLE      | a     | NULL       | index  | NULL          | idx_dep_id | 5       | NULL         |    5 |   100.00 | Using index |
    |  1 | SIMPLE      | b     | NULL       | eq_ref | PRIMARY       | PRIMARY    | 4       | ssm.a.dep_id |    1 |   100.00 | Using index |
    +----+-------------+-------+------------+--------+---------------+------------+---------+--------------+------+----------+-------------+
    2 rows in set, 1 warning (0.00 sec)
  6. const,system
    單表中最多有一個匹配行,查詢起來很是迅速,使用主鍵primary key或者惟一索引unique index進行的查詢。
    mysql> explain select * from t_user where loginname = 'liubei';
    +----+-------------+--------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
    | id | select_type | table  | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra |
    +----+-------------+--------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | t_user | NULL       | const | uk_loginname  | uk_loginname | 303     | const |    1 |   100.00 | NULL  |
    +----+-------------+--------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    

    類型type還有其餘值,如ref_or_null(與ref相似,區別在於條件中包含對NULL的查詢)、index_merge(索引合併優化)、unique_subquery(in 的後面是一個查詢主鍵字段的子查詢)、index_subquery(與 unique_subquery 相似,區別在於 in 的後面是查詢非惟一索引字段的子查詢)等

 

  • possible_keys

    表示查詢時可能使用的索引

  • key

    表示實際使用的索引

  • key_len

    使用到索引字段的長度

  • rows

    掃描行的數據

  • Extra

    執行狀況的說明和描述,包含不適合在其餘列中顯示可是對執行計劃很是重要的額外信息。

    1. Using filesort
      • 排序時沒法使用到索引,就會出現這個,常見於order by和group by中
        mysql> explain select * from t_user order by address;
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
        | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
        |  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |   100.00 | Using filesort |
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+----------------+
        1 row in set, 1 warning (0.00 sec)
        

          全部不是經過索引直接返回排序結果的排序都叫Filesort排序。Filesort並不表明經過磁盤文件進行排序,而只是說明進行了一個排序操做,至於排序操做是否使用了磁盤文件或臨時表等,則取決於MySQL服務器對排序參數的設置和須要排序數據的大小。

        ​       Filesort是經過相應的排序算法,將取得的數據在sort_buffer_size系統變量設置的內存排序區中進行排序,若是內存裝載不下,它就會將磁盤上的數據進行分塊,再對各個數據塊進行排序,而後將各個塊合併成有序的結果集。sort_buffer_size 設置的排序區是每一個線程獨佔的,因此同一個時刻,MySQL中存在多個 sort buffer排序區。瞭解了MySQL排序的方式,優化目標就清晰了:儘可能減小額外的排序,經過索引直接返回有序數據。WHERE條件和ORDER BY使用相同的索引,而且ORDER BY的順序和索引順序相同,而且ORDER BY的字段都是升序或者都是降序。不然確定須要額外的排序操做,這樣就會出現Filesort。

        --引自<深刻淺出Mysql>18.4.3優化ORDER BY語句 章節

    2. Using index
      • 查詢使用到了覆蓋索引,不須要回表,效率不錯
      • 若是同時出現了Using where ,說明索引被用來讀取數據,並在server層執行查找索引鍵值過濾
      • 若是沒有出現Using where,說明索引被用來讀取數據
        mysql> explain select name,age,sex from t_user ;
        +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
        | id | select_type | table  | partitions | type  | possible_keys | key              | key_len | ref  | rows | filtered | Extra       |
        +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
        |  1 | SIMPLE      | t_user | NULL       | index | NULL          | idx_name_age_sex | 312     | NULL |    5 |   100.00 | Using index |
        +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+-------------+
        1 row in set, 1 warning (0.00 sec)
        
        
        
        mysql> explain select name,age,sex from t_user where age > 10;
        +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+
        | id | select_type | table  | partitions | type  | possible_keys | key              | key_len | ref  | rows | filtered | Extra                    |
        +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+
        |  1 | SIMPLE      | t_user | NULL       | index | NULL          | idx_name_age_sex | 312     | NULL |    5 |    33.33 | Using where; Using index |
        +----+-------------+--------+------------+-------+---------------+------------------+---------+------+------+----------+--------------------------+
        1 row in set, 1 warning (0.00 sec)
    3. Using temporary
      • 說明使用了臨時表存儲中間結果
      • 多見於對查詢結果進行order by 或者group by 操做
        mysql> explain select distinct a.id from t_user a,t_dep b where a.dep_id = b.id;
        +----+-------------+-------+------------+-------+--------------------------------------------------+------------+---------+----------+------+----------+------------------------------+
        | id | select_type | table | partitions | type  | possible_keys                                    | key        | key_len | ref      | rows | filtered | Extra                        |
        +----+-------------+-------+------------+-------+--------------------------------------------------+------------+---------+----------+------+----------+------------------------------+
        |  1 | SIMPLE      | b     | NULL       | index | PRIMARY                                          | PRIMARY    | 4       | NULL     |    1 |   100.00 | Using index; Using temporary |
        |  1 | SIMPLE      | a     | NULL       | ref   | PRIMARY,uk_loginname,idx_dep_id,idx_name_age_sex | idx_dep_id | 5       | ssm.b.id |    5 |   100.00 | Using index                  |
        +----+-------------+-------+------------+-------+--------------------------------------------------+------------+---------+----------+------+----------+------------------------------+
        2 rows in set, 1 warning (0.00 sec)
        

          問題:若是變動條件,出現的執行計劃應該是怎樣的?

        explain select distinct a.id from t_user a,t_dep b where a.dep_id = b.id and b.name = 'na';
        

          有興趣回答的讀者能夠評論區回覆。

    4. Using where
      • 表示存儲引擎返回的記錄並非全部的都知足查詢條件,須要在server層進行過濾
        mysql> explain select * from t_user where age > 10;
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
        | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
        |  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |    33.33 | Using where |
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
        
        
        mysql> explain select * from t_user where address = 'beijing';
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
        | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
        |  1 | SIMPLE      | t_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |    20.00 | Using where |
        +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
        1 row in set, 1 warning (0.00 sec)
        
        
        mysql> explain select * from t_user where id in (1,2,3);
        +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
        | id | select_type | table  | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
        +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
        |  1 | SIMPLE      | t_user | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    3 |   100.00 | Using where |
        +----+-------------+--------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
        1 row in set, 1 warning (0.00 sec)
    5. Using index condition
      • 5.6.x以後支持ICP(Index Condition Pushdow)特性,能夠把where條件也下推到存儲引擎層,只不過where條件列須要是普通索引,不符合where條件的數據,直接不讀取,這樣就大大減小了存儲引擎掃描的記錄數量。
      • type值爲range、 ref或者eq_ref時候 , 配合普通索引列添加會使用到索引條件下推技術
      • 使用複合索引時,當不知足最左前綴原則時,也會使用到索引下推技術
        mysql> show index from t_user;
        +--------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
        | Table  | Non_unique | Key_name         | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
        +--------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
        | t_user |          0 | PRIMARY          |            1 | id          | A         |           5 |     NULL | NULL   |      | BTREE      |         |               |
        | t_user |          0 | uk_loginname     |            1 | loginname   | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
        | t_user |          1 | idx_dep_id       |            1 | dep_id      | A         |           1 |     NULL | NULL   | YES  | BTREE      |         |               |
        | t_user |          1 | idx_name_age_sex |            1 | name        | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
        | t_user |          1 | idx_name_age_sex |            2 | age         | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
        | t_user |          1 | idx_name_age_sex |            3 | sex         | A         |           5 |     NULL | NULL   | YES  | BTREE      |         |               |
        +--------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
        6 rows in set (0.00 sec)
        
        
        mysql> explain select * from t_user where id in (1,2,3) and name = 'liubei';
        +----+-------------+--------+------------+------+--------------------------+------------------+---------+-------+------+----------+-----------------------+
        | id | select_type | table  | partitions | type | possible_keys            | key              | key_len | ref   | rows | filtered | Extra                 |
        +----+-------------+--------+------------+------+--------------------------+------------------+---------+-------+------+----------+-----------------------+
        |  1 | SIMPLE      | t_user | NULL       | ref  | PRIMARY,idx_name_age_sex | idx_name_age_sex | 303     | const |    1 |    60.00 | Using index condition |
        +----+-------------+--------+------------+------+--------------------------+------------------+---------+-------+------+----------+-----------------------+
        1 row in set, 1 warning (0.00 sec)
        
        
        
        mysql> explain select * from t_user where  name = 'aa' and sex = '1';
        +----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+-----------------------+
        | id | select_type | table  | partitions | type | possible_keys    | key              | key_len | ref   | rows | filtered | Extra                 |
        +----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+-----------------------+
        |  1 | SIMPLE      | t_user | NULL       | ref  | idx_name_age_sex | idx_name_age_sex | 303     | const |    1 |    20.00 | Using index condition |
        +----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+-----------------------+
        1 row in set, 1 warning (0.00 sec)
        

         

今天的Mysql執行計劃分析就到這了。相信掌握了上面的內容,會對本身也是一個不錯的提高。

上文有誤的地方,歡迎讀者提出來,一塊兒探討。

相關文章
相關標籤/搜索