深入理解 MySql 的 Explain


點擊上方“統計與數據分析實戰”,星標公衆號

重磅乾貨,第一時間送達

☞500g+超全學習資源免費領取

相信大部分入門數據庫的朋友都是從數據庫的“增刪改查”學起的。其實,對於很多搞業務的非專業技術人員而言,可能基本的增刪改查也夠用了,因爲目的並不是要寫的多好,只要能正確查到自己想要的分析的數據就可以了。

但是,對於一個專業搞數據分析的人而言,可就沒那麼簡單了。這個自己平時跑個小數可能也沒啥感覺,但現實工作中當公司業務數據量達到百萬甚至千萬級以上時,一個查詢語句寫的好壞所造成的影響就尤爲明顯了。所以也就不難理解爲什麼面試的時候面試官喜歡問一些關於優化的問題。

爲了瞭解自己寫的SQL是好是壞,MySql提供了Explain執行計劃功能。它對優化SQL語句尤爲的重要,通過它可以看清執行過程的細節,分析查詢語句或是結構的性能瓶頸,找到問題所在。

如何使用Explain?

explain的使用很簡單,就是在select 語句之前增加 explain關鍵字就ok了。MySQL 會在查詢上設置一個標記,執行查詢時,會返回執行計劃的信息,而不是執行這條SQL。比如這樣:

# explain + sql
explain select * from table where a = 1;

Explain執行計劃能做什麼?

  • 確定表的讀取順序

  • 數據讀取操作的操作類型

  • 哪些索引可以使用

  • 哪些索引被實際使用

  • 表之間的引用

  • 每張表有多少行被優化器查詢

可以看出執行計劃給我們提供的信息是非常有幫助的。只有讀懂了這些內容,才能定位問題點在哪,進而去解決。下面東哥給大家介紹一下explain執行計劃的內容。

因爲有些字段光看很難理解,因此建立三個表作爲例子來說明,感興趣的朋友也可以自己跑下試試。

DROP TABLE IF EXISTS `actor`;
CREATE TABLE `actor` (
 `id` int(11) NOT NULL,
 `name` varchar(45) DEFAULT NULL,
 `update_time` datetime DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (1,'a','2017-12-22 15:27:18');
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (2,'b','2017-12-22 15:27:18');
INSERT INTO `actor` (`id`, `name`, `update_time`) VALUES (3,'c','2017-12-22 15:27:18');
DROP TABLE IF EXISTS `film`;
CREATE TABLE `film` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(10) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `film` (`id`, `name`) VALUES (3,'film0');
INSERT INTO `film` (`id`, `name`) VALUES (1,'film1');
INSERT INTO `film` (`id`, `name`) VALUES (2,'film2');
DROP TABLE IF EXISTS `film_actor`;
CREATE TABLE `film_actor` (
 `id` int(11) NOT NULL,
 `film_id` int(11) NOT NULL,
 `actor_id` int(11) NOT NULL,
 `remark` varchar(255) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `idx_film_actor_id` (`film_id`,`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`) VALUES (1,1,1);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`) VALUES (2,1,2);
INSERT INTO `film_actor` (`id`, `film_id`, `actor_id`) VALUES (3,2,1);


注意:上面三張表中,actor主鍵爲id;film主鍵爲id,以name字段爲索引;film_actor表中id爲主鍵,以film_id和actor_id爲聯合索引。

執行計劃的內容介紹

我們在Navicat裏隨便執行一個查詢語句,看看都會返回哪些內容。

explain select (select id from actor limit 1) from film;

執行後的結果不是查詢的數據而是執行計劃的解釋,一共有id,select_type,table,type,possible_keys,key,key_len,ref,rows,Extra這些字段,每個都代表不同的含義,下面詳細介紹。

id

id 決定了每個表的加載和讀取順序。比如你寫了個複雜的嵌套邏輯,有很多子查詢,那每個select執行的順序就可通過id序列號觀察出來。

原則是:id值越大越先被執行。id值相同的按從上到下的順序執行。id爲NULL的最後執行。

1、id相同

explain select * from film, actor, film_actor where film.id=actor.id and film.id=film_actor.id;


2、id不同

explain select (select id from actor limit 1) from film;

select_type

select查詢的類型主要有三大類:

1、簡單類型

SIMPLE:最簡單的select查詢,就是查詢中不包含子查詢或者union,表裏如一。

explain select * from film where id=1;

2、嵌套類型

PRIMARY、SUBQUERY、DERIVED 這三個是用在有嵌套邏輯的語句中的。

PRIMARY:嵌套查詢最外層的部分被標記爲PRIMARY。

SUBQUERY:出現在select或者where後面中的子查詢被標記爲SUBQUERY。

DERIVED:這個其實我理解是SUBQUERY的一種特例,只不過出現的位置比較特殊,是在from後面的子查詢,MySQL會將子查詢結果存放在一個臨時表中,稱爲派生表,因爲這是我們派生出來的,而非原始表。

通過一個例子說明。

explain select (select id from actor where id = 1) from (select * from film) t;

3、組合類型

組合類型包括UNION和UNION RESULT兩個。

UNION:UNION前後如果有兩個select ,那麼把出現在union之後的第二個select標記爲UNION;如果UNION包含在from 子句的子查詢中,外層select將被標記爲DERIVED。

UNION RESULT:從 UNION表獲取結果的select。

通過一個例子說明。

explain select id from actor union all select id from actor;

table

表示正在訪問哪個表,以表的名稱出現。

但是有兩個特殊的情況:

1)當 from 子句中有子查詢(派生表)時,那table就會以 < derivedN > 格式出現。因爲此時查詢所依賴的表是一個我們派生出來的表,即依賴一個 id 爲 N 的子查詢的。比如:

explain select (select id from actor where id = 1) from (select * from film) t;

2)當使用 union 時,UNION RESULT 的 table 值爲 <union1,2>,1和2表示參與 union 的 select 行id。比如:

explain select id from actor union all select id from actor;

type

訪問類型,表示MySQL是如何訪問數據的,是全表掃描還是通過索引等?這是考量sql查詢優化中一個很重要的指標,共分有很多種類型,結果值從好到壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般來說,好的sql查詢至少達到range級別,最好能達到ref。下面挑幾個常見且比較重要的說一下。

1. system

表裏只有一行記錄,這個屬於const類型的特例,一行數據平時很少出現,可以忽略不計。

2. const

表示通過索引一次就找到了,const用於比較primary key 或者 unique索引。因爲只需匹配一行數據,所有很快。如果將主鍵置於where列表中,mysql就能將該查詢轉換爲一個const。

systemconst有啥區別呢?看解釋不太好理解,舉一個例子。

explain select * from (select * from film where id = 1) tmp;

這裏子查詢就是const,而最外層查詢則爲system,爲什麼呢?

因爲子查詢將主鍵id置於where中選擇,我們知道主鍵是有唯一性的,所以這個子查詢就只返回一行記錄,即匹配了一行數據。而外層查詢沒得選,因爲子查詢派生表就給了它一行數據,也就是說它要查詢的表裏就一行數據。因此,system是表裏只有一行數據,const是從表裏選出唯一一條數據,表裏可能很多數據。

3. eq_ref

唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵 或 唯一索引掃描。

explain select * from film_actor left join film on film_actor.film_id = film.id;

4. ref

相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前綴,索引要和某個值相比較,可能會找到多個符合條件的行。舉例如下:

普通索引的簡單查詢

explain select * from film where name = "film1";

關聯表查詢,idx_film_actor_idfilm_idactor_id的聯合索引。這裏使用到了film_actor的左邊前綴film_id部分。

explain select film_id from film left join film_actor on film.id = film_actor.film_id;

5. range

只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了那個索引。一般就是在where語句中出現了bettween<>in等的查詢。這種索引列上的範圍掃描比全索引掃描要好。只需要開始於某個點,結束於另一個點,不用掃描全部索引

explain select * from actor where id > 1;

6. index

Full Index Scan,index與ALL區別爲index類型只遍歷索引樹。這通常比ALL快,因爲索引文件通常比數據文件小。(Index與ALL雖然都是讀全表,但index是從索引中讀取,而ALL是從硬盤讀取)

explain select * from film;

這裏用了查找所有*,但也返回了index,這是因爲這個表裏的兩個字段都是索引,id是主鍵,name也被定位爲索引。

7. all

全表掃描,意味MySQL需要從頭到尾去查找所需要的行。通常情況下這需要增加索引來進行優化了。

explain select * from film_actor;

possible_keys

這一列顯示查詢可能使用哪些索引來查找。explain 時可能出現 possible_keys 有列,而 key 顯示 NULL 的情況,這種情況是因爲表中數據不多,mysql認爲索引對此查詢幫助不大,選擇了全表查詢。

如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查 where 子句看是否可以創造一個適當的索引來提高查詢性能,然後用 explain 查看效果。

key

這一列顯示MySQL實際採用哪個索引來優化對該表的訪問。如果沒有使用索引,則該列是 NULL。如果想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用 force index、ignore index。

key_len

表示索引中使用的字節數,查詢中使用的索引的長度(最大可能長度),並非實際使用長度,理論上長度越短越好。key_len是根據表定義計算而得的,不是通過表內檢索出的

舉例說明:film_actor的聯合索引 idx_film_actor_idfilm_idactor_id 兩個int列組成,並且每個int是4字節。通過結果中的key_len=4可推斷出查詢使用了第一個列:film_id列來執行索引查找。

explain select * from film_actor where film_id = 2;

ref

這一列顯示了在key列記錄的索引中,表查找值所用到的列或常量,常見的有:const(常量),字段名。舉例如下:

ref爲常量

explain select * from film_actor where film_id = 2;

ref爲字段

explain select film_id from film left join film_actor on film.id = film_actor.film_id;

rows

根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數

Extra

最後一列展示額外的信息。有以下幾種重要的值,Using filesortUsing temporaryUsing indexUsing where Using index,``

1、Using filesort

MySQL對數據使用一個外部的索引排序,而不是按照表內的索引進行排序讀取。也就是說mysql無法利用索引完成的排序操作成爲“文件排序” 。這種情況下一般也是要考慮使用索引來優化的。

explain select * from actor order by name;

2、Using temporary

mysql需要創建一張臨時表來處理查詢。出現這種情況一般是要進行優化的,首先是想到用索引來優化。常見於order by 和 group by。

舉例如下:actor.name沒有索引,此時創建了張臨時表。

explain select distinct name from actor;

3、Using index

表示相應的select操作中使用了覆蓋索引(Covering Index),避免了訪問表的數據行,效率高 如果同時出現Using where,表明索引被用來執行索引鍵值的查找 如果沒用同時出現Using where,表明索引用來讀取數據而非執行查找動作。

explain select film_id from film_actor where film_id = 1;

索引非常重要,關於索引會專門寫一篇文章介紹。

參考:

https://blog.csdn.net/belalds/article/details/80728354 https://blog.csdn.net/UncleMoveBrick/article/details/84477527

- end -
100G數據分析、機器學習資料免費領取
1、掃描下方二維碼,添加 統計與數據分析實戰 作者微信
2、可申請入羣,並獲得數據分析、機器學習資料
3、一定要備註:入羣 + 地點 + 學校/公司。例如:入羣+北京+清華。



長按掃碼,申請入羣
(添加人數較多,請耐心等待)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章