一 .介紹
EXPLAIN 命令用於SQL語句的查詢執行計划。這條命令的輸出結果能夠讓我們了解MySQL 優化器是如何執行SQL 語句的。這條命令並沒有提供任何調整建議,但它能夠提供重要的信息幫助你做出調優決策。
先解析一條sql語句,你可以看出現什么內容
EXPLAIN SELECT * FROM person,dept WHERE person.dept_id = dept.did and person.salary >20000
下面咱們詳細的介紹一下 查詢計划的結果列:
二. id : 查詢序列號
查詢序號即為sql語句執行順序
EXPLAIN select * from person where dept_id =(select did from dept where dname ='python');
從 2 個表中查詢,對應輸出 2 行,每行對應一個表, id 列表示執行順序,id 越大,越先執行,id 相同時,由上至下執行。
三.select_type : 查詢類型
select_type 列提供了 對表的查詢類型。最常見的值包括SIMPLE、PRIMARY、DERIVED 和UNION。其他可能的值還有 UNION RESULT、SUBQUERY 等等.
2.1 simple 簡單查詢 (沒有union和子查詢)
對於不包含子查詢和其他復雜語法的簡單查詢,這是一個常見的類型。
EXPLAIN SELECT * FROM person;
2.2 primary 最外層查詢 (在存在子查詢的語句中,最外面的select查詢就是primary)
這是為更復雜的查詢而創建的首要表(也就是最外層的表)。這個類型通常可以在DERIVED 和 UNION 類型混合使用時見到。
2.3 derived 子查詢(在FROM列表中包含的子查詢)
當一個表不是一個物理表時,那么這個就被叫做DERIVED
EXPLAIN SELECT *FROM (SELECT* FROM person LIMIT 5) AS s
2.4 subquery 映射為子查詢(在SELECT或WHERE列表中包含了子查詢)
這個select-type 的值是為使用子查詢而定義的.
EXPLAIN SELECT person.*,(select 2 from person as p2) FROM person where dept_id = (select did from dept where dname='python');
2.5 union 聯合
EXPLAIN SELECT * FROM person union all select * from person ;
2.6 union result 使用聯合的結果
EXPLAIN SELECT * FROM person union select * from person ;
四. table 輸出的行所用的表
EXPLAIN SELECT * FROM person;
注意: table 列是EXPLAIN 命令輸出結果中的一個單獨行的唯一標識符。這個值可能是表名、表的別名或者一個為查詢產生臨時表的標識符,如派生表、子查詢或集合。
五. type 連接類型
type 列代表表示 查詢計划的連接類型, 有多個參數,先從最佳類型到最差類型介紹 重要且困難
性能: null > system/const > eq_ref > ref > ref_or_null >index_merge > range > index > all
4.1 type=NULL 在優化過程中就已得到結果,不用再訪問表或索引。
EXPLAIN SELECT max(id) FROM person;
4.2 type=const/system 常量
在整個查詢過程中這個表最多只會有一條匹配的行,比如主鍵 id=1 就肯定只有一行;
表最多有一個匹配行,const用於比較primary key 或者unique索引。因為只匹配一行數據,所以一定是用到primary key 或者unique 情況下才會是const,看下面這條語句
EXPLAIN SELECT * FROM person where id =2;
所以說可以理解為const是最優化的。
4.3 type=eq_ref 使用有唯一性 索引查找(主鍵或唯一性索引)
對於eq_ref的解釋,mysql手冊是這樣說的:"對於每個來自於前面的表的行組合,從該表中讀取一行。這可能是最好的聯接類型,除了const類型。它用在一個索引的所有部分被聯接使用並且索引是UNIQUE或PRIMARY KEY"。eq_ref可以用於使用=比較帶索引的列。看下面的語句
EXPAIN select * from person,dept where person.id = dept.did;
得到的結果是下圖所示。很明顯,mysql使用eq_ref聯接來處理 dept 表。
4.4 type=ref 非唯一性索引訪問
這是一種索引訪問(有時也叫做索引查找),它返回所有匹配某個單個值的行,然而,它可能會找到多個符合條件的行。因此,它是查找和掃描的混合體,此類索引訪問只有當使用非唯一性索引或者唯一性索引的非唯一性前綴時才會發生。把它叫做ref是因為索引要跟某個參考值相比較。這個參考值或者是一個常數,或者是來自多表查詢前一個表里的結果值。
EXPLAIN select * from person where name='alex';
4.5 ref_or_null 該聯接類型如同ref類似,結果包含空行.
上面這五種情況都是很理想的索引使用情況
4.6 type=range
索引范圍掃描,常見於 <,<=,>,>=,between,in等操作符。
EXPLAIN select * from person where id BETWEEN 1 and 5;
4.7 type=index
該聯接類型與ALL相同都是掃描表,但index只對索引樹進行掃描,而ALL是是對數據表文件的掃描。這通常比ALL快,因為索引文件通常比數據文件小。(也就是說雖然all和Index都是讀全表,但index是從索引中讀取的,而all是從硬盤中讀的)主要優點是避免了排序,因為索引是排好序的。
Extra列中看到“Using index”,說明mysql正在使用覆蓋索引,只掃描索引的數據。
EXPLAIN select id,name from person;
4.8 type=ALL
對於每個來自於先前的表的行組合,進行完整的表掃描。如果表是第一個沒標記const的表,這通常不好,並且通常在它情況下很差。通常可以增加更多的索引而不要使用ALL,使得行能基於前面的表中的常數值或列值被檢索出。
EXPLAIN select * from person;
六. possible_keys :
該 possible_keys列表示MySQL可以從中選擇查找表中的行的索引。如果此列是NULL,則沒有相關的索引。在這種情況下,您可以通過檢查WHERE 子句來檢查是否引用某些適合索引的列,從而提高查詢的性能。如果是這樣,請創建一個適當的索引並使用 EXPLAIN再次檢查查詢 。
另外如果這個列出現大量可能被使用的索引(例如多於3 個), 那么這 意味着備選索引數量太多了,同時也可能提示存在無效的索引。
七. key :
該key 列指出mysql優化器決定選擇使用哪個索引來優化對該表的訪問。一般來說SQL查詢中的每個表都只會使用一個索引。但是也存在索引合並的少數例外情況,如給定表上用到了兩個或者更多索引。查詢過程中由優化器來決定實際使用的索引。如果possible_keys索引列表中沒有適合查找行的索引,那么這個key可能會命名一個不存在於該possible_keys值中的索引 。簡單且重要
八. key_len :
該key_len 列定義了mysql在索引里使用的字節數。如果mysql正在使用的只是索引里的某些列,那么就可以用這個值來算出具體是哪些列。在mysql5.5及以前的版本里,只能使用索引的最左前綴。例如,sakila.film_actor的主鍵是兩個SMALLINT列,並且每個SMALLINT列是兩個字節,那么索引中的每項是4個字節。也即說明key_len通過查找表的定義而被計算出,而不是表中的數據。
在不損失精確性的情況下,長度越短越好.
九. ref :
ref 列顯示使用哪個列或常數與key一起從表中選擇數據行。指出對 key 列所選擇的索引的查找方式,常見的值有 const, func, NULL, 具體字段名。當 key 列為 NULL ,即不使用索引時 。如果值是func,則使用的值是某個函數的結果
create table a11(id int primary key, age int); insert into a11 value(1, 10),(2, 10); mysql> desc select * from a11 where age=10; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | a11 | ALL | NULL | NULL | NULL | NULL | 2 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 注意:當 key 列為 NULL , ref 列也相應為 NULL 。 mysql> desc select * from a11 where id=1; +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | a11 | const | PRIMARY | PRIMARY | 4 | const | 1 | | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 注意:這次 key 列使用了主鍵索引,where id=1 中 1 為常量, ref 列的 const 便是指這種常量。
十.row :
這一列是mysql評估 為了找到所需的行而要讀取的行數。這個數字是內嵌循環關聯計划里的循環數目,也就是說它不是mysql認為它最終要從表里讀取出來的行數,而是mysql為了找到符合查詢的每一點上標准的那些行而必須讀取的行的平均數。
rows 列提供了試圖分析所有存在於累計結果集中的行數目的MySQL 優化器估計值。執行計划很容易描述這個很困難的統計量。
查詢中總的讀操作數量是基於合並之前行的每一行的rows 值的連續積累而得出的。這是一種嵌套行算法。
簡單且重要,數值越大越不好,說明沒有用好索引
十一.Extra:
該列包含 MySQL 查詢的詳細信息。
10.1 Not exists : 不存在信息
10.2 range checked for each record :沒有找到合適的索引
10.3 Using index condition :出現這個說明mysql使用了覆蓋索引,避免訪問了表的數據行,效率不錯!
建表及插入數據: create table a13 (id int primary key, age int); insert into a13 value(1, 10),(2, 10); mysql> explain select id from a13; +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | a13 | NULL | index | NULL | PRIMARY | 4 | NULL| 2 | Using index | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 因為 id 為主鍵索引,索引中直接包含了 id 的值,所以無需訪問表,直接查找索引就能返回結果。 mysql> explain select age from a13; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | a13 | NULL | ALL | NULL | NULL | NULL | NULL| 2 | NULL | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ age 列沒有索引,因此沒有 Using index ,意即需要訪問表。 為 age 列添加索引: create table a14 (id int primary key, age int); insert into a14 value(1, 10),(2, 10); create index age on a14(id, age); mysql> explain select age from a14; +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | a14 | NULL | index| NULL | age | 9 | NULL| 2 |Using index | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+ 現在索引 age 中也包含了 age 列的值,因此不用訪問表便能返回結果了。10.4 using temporary :mysql對查詢結果進行排序的時候使用了一張臨時表。
mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id; +----+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+------------+------+---------------------------------+ | 1 | SIMPLE | p | ALL | NULL | NULL | NULL | NULL | 8 | Using temporary; Using filesort | | 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | test.p.dept_id| 1 | Using where; Using index | 我們發現在執行這條SQL語句時出現了 using temporary,我們再來看看下面這條SQL語句,去掉 條件中 group by 分組 mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did ORDER BY p.dept_id; +----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | 1 | SIMPLE | p | ALL | NULL | NULL | NULL | NULL | 8 | Using filesort | | 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | test.p.dept_id|1 | Using where; Using index | +----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ 而為什么第一個用了臨時表,而第二個沒有用呢? 因為如果有GROUP BY子句,或者如果GROUP BY中的字段都來自其他的表而非連接順序中的第一個表的話,就會創建一個臨時表了。 那么如何解決呢? 咱們為group by 字段添加一個索引 mysql> alter table person add index did_idx(dept_id); Query OK, 0 rows affected mysql> EXPLAIN SELECT p.id,d.did from person p LEFT JOIN dept d ON p.dept_id = d.did group by p.dept_id ORDER BY p.dept_id; +----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ | 1 | SIMPLE | p | index | NULL | did_idx | 5 | NULL | 8 | Using index | | 1 | SIMPLE | d | eq_ref | PRIMARY | PRIMARY | 4 | test.p.dept_id| 1 | Using where; Using index | +----+-------------+-------+--------+---------------+---------+---------+------------+------+--------------------------+ 為什么添加個索引就不會創建臨時表了呢? 原因就在於 SQL查詢時優先在索引樹中執行,如果索引樹滿足不了當前SQL,才會進行數據表查詢,那么現在加了索引,
已經可以滿足查詢條件了,就沒有必要創建臨時表了10.5 using filesort: mysql對數據不是按照表內的索引順序進行讀取,而是使用了其他字段重新排序.
mysql> EXPLAIN select * from person ORDER BY id; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------+ | 1 | SIMPLE | person | index | NULL | PRIMARY | 4 | NULL | 8 | | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------+ 如果我們用聚合主鍵進行排序,則Extra 為null,我們知道在innodb引擎中,主鍵為聚合索引,插入數據就會排好順序.最后說明mysql是按照表內的索引順序進行讀的 再看下面的列子: mysql> EXPLAIN select * from person ORDER BY salary; +----+-------------+--------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | person | ALL | NULL | NULL | NULL | NULL | 8 | Using filesort | +----+-------------+--------+------+---------------+------+---------+------+------+----------------+ 我們使用非主鍵字段進行排序,這是mysql就不能按照表內的索引順序進行讀了.需要讀取數據行后再進行排序處理10.6 using where: 表示 MySQL 服務器從存儲引擎收到查詢數據,再進行“后過濾”(Post-filter)。所謂“后過濾”,就是先讀取整行數據,再檢查此行是否符合 where 句的條件,符合就留下,不符合便丟棄。因為檢查是在讀取行后才進行的,所以稱為“后過濾”。
建表及插入數據: create table a16 (num_a int not null, num_b int not null, key(num_a)); insert into a16 value(1,1),(1,2),(2,1),(2,2); mysql> explain select * from a16 where num_a=1; +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+ | 1 | SIMPLE | a16 | NULL | ref | num_a | num_a | 4 | const| 2 | NULL | +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------+ 雖然查詢中有 where 子句,但只有 num_a=1 一個條件,且 num_a 列存在索引,通過索引便能確定返回的行,無需進行“后過濾”。 所以,並非帶 WHERE 子句就會顯示"Using where"的。 mysql> explain select * from a16 where num_a=1 and num_b=1; +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | a16 | NULL | ref | num_a | num_a | 4 | const | 2 | Using where | +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ 此查詢增加了條件 num_b=1 ,此列沒有索引,但可以看到查詢同樣能使用 num_a 索引。 MySQL 先通過索引 num_a 找到 num_a=1 的行,然后讀取整行數據,
再檢查 num_b 是否等於 1 ,執行過程看上去象這樣: num_a索引|num_b 沒有索引,屬於行數據 +-------+-------+ | num_a | num_b | where 子句(num_b=1) +-------+-------+ | 1 | 1 | 符合 | 1 | 2 | 不符合 | ... | ... | ... +-------+-------+
詳情參考官方文檔: https://dev.mysql.com/doc/refman/5.6/en/using-explain.html
十二. EXPLAIN結果中哪些信息要引起關注
們使用EXPLAIN解析SQL執行計划時,如果有下面幾種情況,就需要特別關注下了:
首先看下 type 這列的結果,如果有類型是 ALL 時,表示預計會進行全表掃描(full table scan)。通常全表掃描的代價是比較大的,建議創建適當的索引,通過索引檢索避免全表掃描。
再來看下 Extra 列的結果,如果有出現 Using temporary 或者 Using filesort 則要多加關注:
Using temporary,表示需要創建臨時表以滿足需求,通常是因為GROUP BY的列沒有索引,或者GROUP BY和ORDER BY的列不一樣,也需要創建臨時表,建議添加適當的索引。
Using filesort,表示無法利用索引完成排序,也有可能是因為多表連接時,排序字段不是驅動表中的字段,因此也沒辦法利用索引完成排序,建議添加適當的索引。
Using where,通常是因為全表掃描或全索引掃描時(type 列顯示為 ALL 或 index),又加上了WHERE條件,建議添加適當的索引。
其他狀態例如:Using index、Using index condition、Using index for group-by 則都還好,不用緊張。