深入理解 MySql 的 Explain
點擊上方“Python數(shù)據(jù)科學(xué)”,星標(biāo)公眾號
相信大部分入門數(shù)據(jù)庫的朋友都是從數(shù)據(jù)庫的“增刪改查”學(xué)起的。其實,對于很多搞業(yè)務(wù)的非專業(yè)技術(shù)人員而言,可能基本的增刪改查也夠用了,因為目的并不是要寫的多好,只要能正確查到自己想要的分析的數(shù)據(jù)就可以了。
但是,對于一個專業(yè)搞數(shù)據(jù)分析的人而言,可就沒那么簡單了。這個自己平時跑個小數(shù)可能也沒啥感覺,但現(xiàn)實工作中當(dāng)公司業(yè)務(wù)數(shù)據(jù)量達到百萬甚至千萬級以上時,一個查詢語句寫的好壞所造成的影響就尤為明顯了。所以也就不難理解為什么面試的時候面試官喜歡問一些關(guān)于優(yōu)化的問題。
為了了解自己寫的SQL是好是壞,MySql提供了Explain執(zhí)行計劃功能。它對優(yōu)化SQL語句尤為的重要,通過它可以看清執(zhí)行過程的細節(jié),分析查詢語句或是結(jié)構(gòu)的性能瓶頸,找到問題所在。
如何使用Explain?
explain的使用很簡單,就是在select 語句之前增加 explain關(guān)鍵字就ok了。MySQL 會在查詢上設(shè)置一個標(biāo)記,執(zhí)行查詢時,會返回執(zhí)行計劃的信息,而不是執(zhí)行這條SQL。比如這樣:
# explain + sql
explain select * from table where a = 1;
Explain執(zhí)行計劃能做什么?
-
確定表的讀取順序 -
數(shù)據(jù)讀取操作的操作類型 -
哪些索引可以使用 -
哪些索引被實際使用 -
表之間的引用 -
每張表有多少行被優(yōu)化器查詢
可以看出執(zhí)行計劃給我們提供的信息是非常有幫助的。只有讀懂了這些內(nèi)容,才能定位問題點在哪,進而去解決。下面東哥給大家介紹一下explain執(zhí)行計劃的內(nèi)容。
因為有些字段光看很難理解,因此建立三個表作為例子來說明,感興趣的朋友也可以自己跑下試試。
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為聯(lián)合索引。
執(zhí)行計劃的內(nèi)容介紹
我們在Navicat里隨便執(zhí)行一個查詢語句,看看都會返回哪些內(nèi)容。
explain select (select id from actor limit 1) from film;

執(zhí)行后的結(jié)果不是查詢的數(shù)據(jù)而是執(zhí)行計劃的解釋,一共有id,select_type,table,type,possible_keys,key,key_len,ref,rows,Extra這些字段,每個都代表不同的含義,下面詳細介紹。
id
id 決定了每個表的加載和讀取順序。比如你寫了個復(fù)雜的嵌套邏輯,有很多子查詢,那每個select執(zhí)行的順序就可通過id序列號觀察出來。
原則是:id值越大越先被執(zhí)行。id值相同的按從上到下的順序執(zhí)行。id為NULL的最后執(zhí)行。
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:嵌套查詢最外層的部分被標(biāo)記為PRIMARY。
SUBQUERY:出現(xiàn)在select或者where后面中的子查詢被標(biāo)記為SUBQUERY。
DERIVED:這個其實我理解是SUBQUERY的一種特例,只不過出現(xiàn)的位置比較特殊,是在from后面的子查詢,MySQL會將子查詢結(jié)果存放在一個臨時表中,稱為派生表,因為這是我們派生出來的,而非原始表。
通過一個例子說明。
explain select (select id from actor where id = 1) from (select * from film) t;

3、組合類型
組合類型包括UNION和UNION RESULT兩個。
UNION:UNION前后如果有兩個select ,那么把出現(xiàn)在union之后的第二個select標(biāo)記為UNION;如果UNION包含在from 子句的子查詢中,外層select將被標(biāo)記為DERIVED。
UNION RESULT:從 UNION表獲取結(jié)果的select。
通過一個例子說明。
explain select id from actor union all select id from actor;

table
表示正在訪問哪個表,以表的名稱出現(xiàn)。
但是有兩個特殊的情況:
1)當(dāng) from 子句中有子查詢(派生表)時,那table就會以 < derivedN > 格式出現(xiàn)。因為此時查詢所依賴的表是一個我們派生出來的表,即依賴一個 id 為 N 的子查詢的。比如:
explain select (select id from actor where id = 1) from (select * from film) t;

2)當(dāng)使用 union 時,UNION RESULT 的 table 值為
,1和2表示參與 union 的 select 行id。比如:
explain select id from actor union all select id from actor;

type
訪問類型,表示MySQL是如何訪問數(shù)據(jù)的,是全表掃描還是通過索引等?這是考量sql查詢優(yōu)化中一個很重要的指標(biāo),共分有很多種類型,結(jié)果值從好到壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般來說,好的sql查詢至少達到range級別,最好能達到ref。下面挑幾個常見且比較重要的說一下。
1. system
表里只有一行記錄,這個屬于const類型的特例,一行數(shù)據(jù)平時很少出現(xiàn),可以忽略不計。
2. const
表示通過索引一次就找到了,const用于比較primary key 或者 unique索引。因為只需匹配一行數(shù)據(jù),所有很快。如果將主鍵置于where列表中,mysql就能將該查詢轉(zhuǎn)換為一個const。
system和const有啥區(qū)別呢?看解釋不太好理解,舉一個例子。
explain select * from (select * from film where id = 1) tmp;

這里子查詢就是const,而最外層查詢則為system,為什么呢?
因為子查詢將主鍵id置于where中選擇,我們知道主鍵是有唯一性的,所以這個子查詢就只返回一行記錄,即匹配了一行數(shù)據(jù)。而外層查詢沒得選,因為子查詢派生表就給了它一行數(shù)據(jù),也就是說它要查詢的表里就一行數(shù)據(jù)。因此,system是表里只有一行數(shù)據(jù),const是從表里選出唯一一條數(shù)據(jù),表里可能很多數(shù)據(jù)。
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";

關(guān)聯(lián)表查詢,idx_film_actor_id是film_id和actor_id的聯(lián)合索引。這里使用到了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語句中出現(xiàn)了bettween、<、>、in等的查詢。這種索引列上的范圍掃描比全索引掃描要好。只需要開始于某個點,結(jié)束于另一個點,不用掃描全部索引
explain select * from actor where id > 1;

6. index
Full Index Scan,index與ALL區(qū)別為index類型只遍歷索引樹。這通常比ALL快,因為索引文件通常比數(shù)據(jù)文件小。(Index與ALL雖然都是讀全表,但index是從索引中讀取,而ALL是從硬盤讀?。?/p>
explain select * from film;

這里用了查找所有*,但也返回了index,這是因為這個表里的兩個字段都是索引,id是主鍵,name也被定位為索引。
7. all
全表掃描,意味MySQL需要從頭到尾去查找所需要的行。通常情況下這需要增加索引來進行優(yōu)化了。
explain select * from film_actor;

possible_keys
這一列顯示查詢可能使用哪些索引來查找。explain 時可能出現(xiàn) possible_keys 有列,而 key 顯示 NULL 的情況,這種情況是因為表中數(shù)據(jù)不多,mysql認(rèn)為索引對此查詢幫助不大,選擇了全表查詢。
如果該列是NULL,則沒有相關(guān)的索引。在這種情況下,可以通過檢查 where 子句看是否可以創(chuàng)造一個適當(dāng)?shù)乃饕齺硖岣卟樵冃阅?,然后?explain 查看效果。
key
這一列顯示MySQL實際采用哪個索引來優(yōu)化對該表的訪問。如果沒有使用索引,則該列是 NULL。如果想強制MySQL使用或忽視possible_keys列中的索引,在查詢中使用 force index、ignore index。
key_len
表示索引中使用的字節(jié)數(shù),查詢中使用的索引的長度(最大可能長度),并非實際使用長度,理論上長度越短越好。key_len是根據(jù)表定義計算而得的,不是通過表內(nèi)檢索出的
舉例說明:film_actor的聯(lián)合索引 idx_film_actor_id 由 film_id 和 actor_id 兩個int列組成,并且每個int是4字節(jié)。通過結(jié)果中的key_len=4可推斷出查詢使用了第一個列:film_id列來執(zhí)行索引查找。
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
根據(jù)表統(tǒng)計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數(shù)
Extra
最后一列展示額外的信息。有以下幾種重要的值,Using filesort,Using temporary,Using index,Using where Using index,``
1、Using filesort
MySQL對數(shù)據(jù)使用一個外部的索引排序,而不是按照表內(nèi)的索引進行排序讀取。也就是說mysql無法利用索引完成的排序操作成為“文件排序” 。這種情況下一般也是要考慮使用索引來優(yōu)化的。
explain select * from actor order by name;

2、Using temporary
mysql需要創(chuàng)建一張臨時表來處理查詢。出現(xiàn)這種情況一般是要進行優(yōu)化的,首先是想到用索引來優(yōu)化。常見于order by 和 group by。
舉例如下:actor.name沒有索引,此時創(chuàng)建了張臨時表。
explain select distinct name from actor;

3、Using index
表示相應(yīng)的select操作中使用了覆蓋索引(Covering Index),避免了訪問表的數(shù)據(jù)行,效率高 如果同時出現(xiàn)Using where,表明索引被用來執(zhí)行索引鍵值的查找 如果沒用同時出現(xiàn)Using where,表明索引用來讀取數(shù)據(jù)而非執(zhí)行查找動作。
explain select film_id from film_actor where film_id = 1;

索引非常重要,關(guān)于索引會專門寫一篇文章介紹。
參考:
https://blog.csdn.net/belalds/article/details/80728354 https://blog.csdn.net/UncleMoveBrick/article/details/84477527
- end -
推薦閱讀 GitHub 4K+Star!SpaceX火箭數(shù)據(jù)開放API接口,可用Python進行抓取分析
你真的會用 Postman 嗎? 詳盡實用的 PyCharm 教程,這篇文章值得一看 你在打王者農(nóng)藥,有人卻用iPhone來訓(xùn)練神經(jīng)網(wǎng)絡(luò) 打基礎(chǔ)一定要吃透這12類 Python 內(nèi)置函數(shù) 完整中文版 | 2019 Python 官方年度報告強勢來襲! 一二線城市知名IT互聯(lián)網(wǎng)公司名單!
100G數(shù)據(jù)分析、機器學(xué)習(xí)資料免費領(lǐng)取 1、掃描下方二維碼,添加 Python數(shù)據(jù)科學(xué) 作者微信 2、可申請入群,并獲得數(shù)據(jù)分析、機器學(xué)習(xí)資料 3、一定要備注:入群 + 地點 + 學(xué)校/公司。例如:入群+北京+清華。
長按掃碼,申請入群 (添加人數(shù)較多,請耐心等待)
