深刻理解 MySql 的 Explain

timg.jpg

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

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

爲了瞭解本身寫的SQL是好是壞,MySql提供了Explain執行計劃功能。它對優化SQL語句尤其的重要,經過它能夠看清執行過程的細節,分析查詢語句或是結構的性能瓶頸,找到問題所在。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裏隨便執行一個查詢語句,看看都會返回哪些內容。spa

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

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

id

id 決定了每一個表的加載和讀取順序。好比你寫了個複雜的嵌套邏輯,有不少子查詢,那每一個select執行的順序就可經過id序列號觀察出來。3d

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

一、id相同

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

image.png

二、id不一樣

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

image.png

select_type

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

一、簡單類型

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

explain select * from film where id=1;

image.png

二、嵌套類型

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;

image.png

三、組合類型

組合類型包括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;

image.png

table

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

可是有兩個特殊的狀況:

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

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

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

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

image.png

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;

image.png
這裏子查詢就是const,而最外層查詢則爲system,爲何呢?

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

3. eq_ref

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

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

image.png

4. ref

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

普通索引的簡單查詢

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

image.png
關聯表查詢,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;

image.png

5. range

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

explain select * from actor where id > 1;

image.png

6. index

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

explain select * from film;

image.png
這裏用了查找全部*,但也返回了index,這是由於這個表裏的兩個字段都是索引,id是主鍵,name也被定位爲索引。

7. all

全表掃描,意味MySQL須要從頭至尾去查找所須要的行。一般狀況下這須要增長索引來進行優化了。

explain select * from film_actor;

image.png

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;

image.png

ref

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

ref爲常量

explain select * from film_actor where film_id = 2;

image.png

ref爲字段

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

image.png

rows

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

Extra

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

一、Using filesort

MySQL對數據使用一個外部的索引排序,而不是按照表內的索引進行排序讀取。也就是說mysql沒法利用索引完成的排序操做成爲「文件排序」 。這種狀況下通常也是要考慮使用索引來優化的。

explain select * from actor order by name;

image.png

二、Using temporary

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

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

explain select distinct name from actor;

image.png

三、Using index

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

explain select film_id from film_actor where film_id = 1;

image.png

關於索引會專門寫一篇文章介紹。

參考:

https://blog.csdn.net/belalds...
https://blog.csdn.net/UncleMo...

最後,若是喜歡本篇文章,歡迎點贊和收藏。
更多精彩內容請關注個人微信公衆號:Python數據科學

相關文章
相關標籤/搜索