天天看點

【MYSQL】EXPLAIN指令EXPLAIN指令idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra

EXPLAIN指令

EXPLAIN指令是檢視優化器如何決定執行查詢的主要方法,但最終不一定就是按照這個來。

EXPLAIN可以作用于 SELECT、DELETE、INSERT、UPDATE和REPLACE語句。

可以使用FORMAT=JSON來輸出詳細的執行計劃成本

大緻是以下的格式

{
    "query_block": {
        "select_id": 1,
        "cost_info": {
            "query_cost": "399.75"
        },
        "table": {
            "table_name": "zc_order",
            "access_type": "index",
            "key": "IDX_ORDER_NO",
            "used_key_parts": [
                "ORDER_NO"
            ],
            "key_length": "98",
            "rows_examined_per_scan": 3755,
            "rows_produced_per_join": 3755,
            "filtered": "100.00",
            "using_index": true,
            "cost_info": {
                "read_cost": "24.25",
                "eval_cost": "375.50",
                "prefix_cost": "399.75",
                "data_read_per_join": "14M"
            },
            "used_columns": [
                "ID"
            ]
        }
    }
}
           

執行計劃包含的資訊

+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                        |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
           

詳見:MySQL官方文檔-explain-output

下面具體看看每一項的含義

id

包含一組數字,表示查詢中執行select子句或操作表的順序

id相同,表示同一組,執行順序由上至下

id不同則值越大優先級越高,越先執行

如果是子查詢,id的序号會遞增,id值越大優先級越高,越先被執行

臨時表的id是NULL

select_type

查詢中每個select子句的類型(簡單OR複雜)

select_type對于非SELECT語句,顯示的是其類型,例如UPDATE顯示UPDATE

  • SIMPLE:簡單的SELECT,查詢中不包含子查詢或者UNION
    mysql> EXPLAIN SELECT * FROM zc_order;
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | zc_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 3781 |      100 | NULL  |
    +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
               
  • PRIMARY:最外層的SELECT,查詢中若包含任何複雜的子部分,最外層查詢則被标記
  • SUBQUERY:在SELECT或WHERE清單中包含了子查詢,該子查詢被标記
    mysql> EXPLAIN SELECT * from zc_order_goods where order_no = (select MAX(order_no) from zc_order);
    +----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    | id | select_type | table          | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                        |
    +----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    |  1 | PRIMARY     | zc_order_goods | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4696 |       10 | Using where                  |
    |  2 | SUBQUERY    | NULL           | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | NULL     | Select tables optimized away |
    +----+-------------+----------------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
               
  • UNION:标記出現在UNION後的SELECT
  • UNION RESULT:UNION的結果
    mysql> EXPLAIN SELECT * from zc_order o where o.order_no = 'UNO200418000000008' UNION SELECT * from zc_order o2 where o2.order_no = 'UNO200418000000009';
    +------+--------------+------------+------------+-------+---------------+--------------+---------+-------+------+----------+-----------------+
    | id   | select_type  | table      | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra           |
    +------+--------------+------------+------------+-------+---------------+--------------+---------+-------+------+----------+-----------------+
    |    1 | PRIMARY      | o          | NULL       | const | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | const |    1 |      100 | NULL            |
    |    2 | UNION        | o2         | NULL       | const | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | const |    1 |      100 | NULL            |
    | NULL | UNION RESULT | <union1,2> | NULL       | ALL   | NULL          | NULL         | NULL    | NULL  | NULL | NULL     | Using temporary |
    +------+--------------+------------+------------+-------+---------------+--------------+---------+-------+------+----------+-----------------+
               
  • DERIVED:用來表示包含在FROM子句中的子查詢,mysql會遞歸執行并将結果放到一個臨時表中。伺服器内部稱為"派生表",因為該臨時表是從子查詢中派生出來的。(派生表的性能不好)
    --  MySQL 5.7開始優化器引入derived_merge,
    -- 當子查詢中存在 UNION、GROUP BY、DISTINCT、LIMIT/OFFSET以及聚合操作時會禁止該優化
    mysql> EXPLAIN SELECT * FROM ( SELECT * FROM zc_order LIMIT 1) temp;
    +----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table      | partitions | type   | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
    |  1 | PRIMARY     | <derived2> | NULL       | system | NULL          | NULL | NULL    | NULL |    1 |      100 | NULL  |
    |  2 | DERIVED     | zc_order   | NULL       | ALL    | NULL          | NULL | NULL    | NULL | 3781 |      100 | NULL  |
    +----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
               
  • DEPENDENT DERIVED:表示派生表依賴于另一個表
  • SUBQUERY和UNION還可以被标記為DEPENDENT和UNCACHEABLE,DEPENDENT意味着SELECT在子查詢中并且依賴于外層查詢。UNCACHEABLE意味着無法緩存其結果的子查詢,必須為外部查詢的每一行重新計算其結果。
    mysql> EXPLAIN SELECT * from zc_order where order_no in 
    (SELECT order_no from zc_order o where o.order_no = 'UNO200418000000008' UNION SELECT order_no from zc_order o2 where o2.order_no = 'UNO200418000000009');
    +------+--------------------+------------+------------+-------+---------------+--------------+---------+-------+------+----------+-----------------+
    | id   | select_type        | table      | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra           |
    +------+--------------------+------------+------------+-------+---------------+--------------+---------+-------+------+----------+-----------------+
    |    1 | PRIMARY            | zc_order   | NULL       | ALL   | NULL          | NULL         | NULL    | NULL  | 3781 |      100 | Using where     |
    |    2 | DEPENDENT SUBQUERY | o          | NULL       | const | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | const |    1 |      100 | Using index     |
    |    3 | DEPENDENT UNION    | o2         | NULL       | const | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | const |    1 |      100 | Using index     |
    | NULL | UNION RESULT       | <union2,3> | NULL       | ALL   | NULL          | NULL         | NULL    | NULL  | NULL | NULL     | Using temporary |
    +------+--------------------+------------+------------+-------+---------------+--------------+---------+-------+------+----------+-----------------+
               
  • MATERIALIZED 物化子查詢,用于實作對子查詢的優化,對應FORMAT=JSON輸出的

    materialized_from_subquery

    詳見:MySQL官方文檔-subquery-materialization

table

表示該行所通路的表

<derivedN>

表示目前查詢依賴于id=N的查詢

<unionM,N>

表示id=M、N的查詢參與了UNION

<subqueryN>

id=N的物化子查詢結果

partitions

如果查詢是基于分區表的話,會顯示查詢将通路的分區

type

表示MySQL在表中找到所需行的方式,又稱“通路類型”,常見類型如下:

現有索引

ALTER TABLE zc_order ADD PRIMARY KEY (ID);

ALTER TABLE zc_order ADD UNIQUE INDEX IDX_ORDER_NO (ORDER_NO);

ALTER TABLE zc_order ADD INDEX IDX_USER_NO_ORDER_STATUS (USER_NO, ORDER_STATUS);

all: Full Table Scan, MySQL将周遊全表以找到比對的行

mysql> EXPLAIN SELECT * FROM zc_order;
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | zc_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 3781 |      100 | NULL  |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------+
           

index: Full Index Scan,index與ALL差別為index類型隻周遊索引樹

mysql> EXPLAIN SELECT id, order_no FROM zc_order;
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | zc_order | NULL       | index | NULL          | IDX_ORDER_NO | 130     | NULL | 3781 |      100 | Using index |
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
           

range: 索引範圍掃描,顯而易見的索引範圍掃描是帶有

between、!=、<>、in、not in、or

的查詢(or兩邊必須都為索引)

mysql> EXPLAIN SELECT * FROM zc_order WHERE order_no = 'UNO200418000000008' or order_no = 'UNO200418000000009';
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
| id | select_type | table    | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | zc_order | NULL       | range | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | NULL |    2 |      100 | Using index condition |
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
           

ref: 使用非唯一索引掃描或者唯一索引的字首掃描(組合索引),傳回比對某個單獨值的記錄行

-- 非唯一索引,走ref
-- 唯一索引,如果是單列索引走的是const, 如果是組合索引的字首比對走ref
mysql> EXPLAIN SELECT * FROM zc_order WHERE USER_NO = 'US0000000001';
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys            | key                      | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | zc_order | NULL       | ref  | IDX_USER_NO_ORDER_STATUS | IDX_USER_NO_ORDER_STATUS | 258     | const |    4 |      100 | NULL  |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
           

eq_ref: 類似ref,差別就在使用的索引是主鍵索引或者唯一索引,對于每個索引鍵值,表中隻有一條記錄比對

-- 單表沒測試出來,通常是const
-- 連表查詢
mysql> EXPLAIN SELECT * FROM zc_order o, zc_order_goods g WHERE o.ORDER_NO = g.ORDER_NO;
+----+-------------+-------+------------+--------+---------------+--------------+---------+----------------------+------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys | key          | key_len | ref                  | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+--------------+---------+----------------------+------+----------+-------+
|  1 | SIMPLE      | g     | NULL       | ALL    | NULL          | NULL         | NULL    | NULL                 | 4696 |      100 | NULL  |
|  1 | SIMPLE      | o     | NULL       | eq_ref | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | test-db.g.ORDER_NO   |    1 |      100 | NULL  |
+----+-------------+-------+------------+--------+---------------+--------------+---------+----------------------+------+----------+-------+
           

const: 當MySQL對查詢某部分進行優化,并轉換為一個常量時,使用這些類型通路。例如使用主鍵或唯一索引進行查詢

mysql> EXPLAIN SELECT * FROM zc_order WHERE order_no = 'UNO200418000000008';
+----+-------------+----------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | zc_order | NULL       | const | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | const |    1 |      100 | NULL  |
+----+-------------+----------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
           

system: system是const類型的特例,當查詢的表隻有一行的情況下,使用system

NULL: MySQL在優化過程中分解語句,執行時甚至不用通路表或索引,例如從一個索引列裡選取最小值可以通過單獨索引查找完成

mysql> EXPLAIN SELECT MIN(order_no) FROM zc_order;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
| 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 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
           

從上到下,性能越來越好

possible_keys

指出MySQL能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引将被列出,但不一定被查詢使用

有可能出現

possible_keys

不為null,但是

key

為null的情況,一般是查詢條件上有符合的索引,但是mysql優化器認為走全表掃描效率更高

mysql> EXPLAIN SELECT * FROM zc_order where user_no > 'US0000000013';
+----+-------------+----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys            | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | zc_order | NULL       | ALL  | IDX_USER_NO_ORDER_STATUS | NULL | NULL    | NULL | 3781 |    90.27 | Using where |
+----+-------------+----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
           

另一總情況剛好相反,possible_keys為null,但是key不為null,通常是使用了覆寫索引導緻的

-- 這裡比較有趣的一點是,覆寫的索引為id,任意索引中都存在主鍵 mysql根據某種規則選擇了其中一個而不是直接拿主鍵索引
-- 如果查詢的是user_no,那麼必定是使用IDX_USER_NO_ORDER_STATUS索引
mysql> EXPLAIN SELECT id FROM zc_order;
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys | key          | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | zc_order | NULL       | index | NULL          | IDX_ORDER_NO | 130     | NULL | 3781 |      100 | Using index |
+----+-------------+----------+------------+-------+---------------+--------------+---------+------+------+----------+-------------+
           

key

實際用到的索引

key_len

表示索引中使用的位元組數,可通過該列計算查詢中使用的索引的長度(key_len顯示的值為索引字段的最大可能長度,并非實際使用長度,即key_len是根據表定義計算而得,不是通過表内檢索出的)

  • 所有的索引字段,如果沒有設定not null,則需要加一個位元組用于記錄是否為NULL
  • 定長字段,int占四個位元組、date占三個位元組、char(n)占n個字元,tinyint占一個位元組
  • 對于變長字段varchar(n),則有n個字元+兩個位元組。
  • 不同的字元集,一個字元占用的位元組數不同。latin1編碼的一個字元占用一個位元組,gbk編碼的一個字元占用兩個位元組,utf8編碼的一個字元占用三個位元組,utf8mb4編碼的一個字元占用四個位元組
-- `ORDER_NO` varchar(32) CHARACTER SET utf8 NOT NULL DEFAULT ''
-- 32 * 3 + 2 = 98
mysql> EXPLAIN SELECT * FROM zc_order where order_no = 'UNO200515000000060';
+----+-------------+----------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type  | possible_keys | key          | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | zc_order | NULL       | const | IDX_ORDER_NO  | IDX_ORDER_NO | 98      | const |    1 |      100 | NULL  |
+----+-------------+----------+------------+-------+---------------+--------------+---------+-------+------+----------+-------+

-- `USER_NO` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT ''
-- `ORDER_STATUS` char(2) CHARACTER SET utf8 NOT NULL DEFAULT ''
-- 64 * 3 + 2 + 2 * 3 = 200
mysql> EXPLAIN SELECT * FROM zc_order where user_no = 'US0000000013' and order_status = '01';
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys            | key                      | key_len | ref         | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | zc_order | NULL       | ref  | IDX_USER_NO_ORDER_STATUS | IDX_USER_NO_ORDER_STATUS | 200     | const,const |    1 |      100 | NULL  |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
           

ref

表示索引的哪些列被使用了,可能是一個常量

常見的有:const、字段名、NULL

-- const
mysql> EXPLAIN SELECT * FROM zc_order WHERE id = 20;
+----+-------------+----------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra                             |
+----+-------------+----------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------------------------+
|  1 | SIMPLE      | zc_order | NULL       | const | PRIMARY       | PRIMARY | 8       | const |    1 |      100 | Directly search via Primary Index |
+----+-------------+----------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------------------------+

-- 字段名
mysql> EXPLAIN SELECT * FROM zc_order o, zc_order_goods g WHERE o.ORDER_NO = g.ORDER_NO;
+----+-------------+-------+------------+--------+---------------+--------------+---------+----------------------+------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys | key          | key_len | ref                  | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+--------------+---------+----------------------+------+----------+-------+
|  1 | SIMPLE      | g     | NULL       | ALL    | NULL          | NULL         | NULL    | NULL                 | 4696 |      100 | NULL  |
|  1 | SIMPLE      | o     | NULL       | eq_ref | IDX_ORDER_NO  | IDX_ORDER_NO | 130     | test-db.g.ORDER_NO   |    1 |      100 | NULL  |
+----+-------------+-------+------------+--------+---------------+--------------+---------+----------------------+------+----------+-------+
           

rows

表示MySQL根據表統計資訊及索引選用情況,估算的找到所需的記錄所需要讀取的行數

filtered

這個字段表示存儲引擎傳回的資料在server層過濾後,剩下多少滿足查詢的記錄數量的百分比(估計)

這個字段幾乎沒什麼用,通常都是顯示100%,隻對索引類型為

index

all

時才生效(where條件中出現的列上建有索引,但是執行計劃不走索引的range或ref掃描,而走全表掃描或覆寫索引掃描)

mysql> EXPLAIN SELECT * FROM zc_order where user_no > 'US0000000013';
+----+-------------+----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys            | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | zc_order | NULL       | ALL  | IDX_USER_NO_ORDER_STATUS | NULL | NULL    | NULL | 3755 |    49.99 | Using where |
+----+-------------+----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
1 row in set
           

Extra

包含不适合在其他列中顯示但十分重要的額外資訊

  • Using where

    表示mysql伺服器将在存儲引擎檢索後再進行過濾(service層過濾)。許多where條件裡涉及索引中的列,當它讀取索引時,能夠直接在索引中進行過濾,是以不是所有帶where的查詢都會顯示"Using where"

  • Using index

    該值表示相應的select操作中使用了覆寫索引(Covering Index)

  • Using index confition

    表示使用到了索引下推

【MYSQL】EXPLAIN指令EXPLAIN指令idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra
  • Using temporary

    表示MySQL需要使用臨時表來存儲結果集,常見于排序和分組查詢

  • Using filesort

    MySQL必須做一個額外的步驟來找出如何按排序的順序檢索行

  • Using join buffer

    該值強調了在擷取連接配接條件時沒有使用索引,并且需要連接配接緩沖區來存儲中間結果。如果出現了這個值,那應該注意,根據查詢的具體情況可能需要添加索引來改進能

  • Impossible where

    這個值強調了where語句會導緻沒有符合條件的行

  • Select tables optimized away

    這個值意味着僅通過使用索引,優化器可能僅從聚合函數結果中傳回一行。

    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
    | 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 |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------------------+
               
  • Index merges

    當MySQL 決定要在一個給定的表上使用超過一個索引的時候,就會出現以下格式中的一個,詳細說明使用的索引以及合并的類型

    Using sort_union(…)

    Using union(…)

    Using intersect(…)

繼續閱讀