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輸出的
詳見:MySQL官方文檔-subquery-materializationmaterialized_from_subquery
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
表示使用到了索引下推
-
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(…)