MySQL高級知識(四)——Explain

前言:explain(執行計劃),使用explain關鍵字可以模擬優化器執行sql查詢語句,從而知道MySQL是如何處理sql語句。explain主要用於分析查詢語句或表結構的性能瓶頸。

注:本系列隨筆如無特殊說明都MySQL版本都爲5.7.22。

1.explain的作用

通過explain+sql語句可以知道如下內容:

①表的讀取順序。(對應id)

②數據讀取操作的操作類型。(對應select_type)

③哪些索引可以使用。(對應possible_keys)

④哪些索引被實際使用。(對應key)

⑤表直接的引用。(對應ref)

⑥每張表有多少行被優化器查詢。(對應rows)

2.explain包含的信息

explain使用:explain+sql語句,通過執行explain可以獲得sql語句執行的相關信息。

下面對explain的表頭字段含義進行解釋。

注:下圖中有些explain表頭不包含partitions和filtered字段,是因爲有些截圖是直接從視頻資料中截取的,當並不影響我們的分析。

①id

select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序,該字段通常與table字段搭配來分析。

#1.id相同,執行順序從上到下。

id相同,執行順序從上到下,搭配table列進行觀察可知,執行順序爲t1->t3->t2。

#2.id不同,如果是子查詢,id的序號會遞增,id值越大執行優先級越高。

如果是子查詢id的序號會遞增,id值越大執行優先級越高,搭配table列可知,執行順序爲t3->t1->t2。

#3.id相同不同,同時存在。

id如果相同,可認爲是同一組,執行順序從上到下。在所有組中,id值越大執行優先級越高。所以執行順序爲t3->derived2(衍生表,也可以說臨時表)->t2。

總結:id的值表示select子句或表的執行順序,id相同,執行順序從上到下,id不同,值越大的執行優先級越高。

②select_type

查詢的類型,主要用於區別普通查詢、聯合查詢、子查詢等複雜的查詢。其值主要有六個:

#1.SIMPLE

簡單的select查詢,查詢中不包含子查詢或union查詢。

#2.PRIMARY

查詢中若包含任何複雜的子部分,最外層查詢爲PRIMARY,也就是最後加載的就是PRIMARY。

#3.SUBQUERY

在select或where列表中包含了子查詢,就爲被標記爲SUBQUERY。

#4.DERIVED

在from列表中包含的子查詢會被標記爲DERIVED(衍生),MySQL會遞歸執行這些子查詢,將結果放在臨時表中。

#5.UNION

若第二個select出現在union後,則被標記爲UNION,若union包含在from子句的子查詢中,外層select將被標記爲DERIVED。

#6.UNION RESULT

從union表獲取結果的select。

③table

顯示sql操作屬於哪張表的。

④partitions

官方定義爲The matching partitions(匹配的分區),該字段應該是看table所在的分區吧(不曉得理解錯誤沒)。值爲NULL表示表未被分區。

⑤type

表示查詢所使用的訪問類型,type的值主要有八種,該值表示查詢的sql語句好壞,從最好到最差依次爲:system>const>eq_ref>ref>range>index>ALL。

要詳細瞭解type取值的作用,需要用數據說話。創建tb_emp(員工表)和tb_dept(部門表)。

a)tb_emp表。

複製代碼

DROP TABLE IF EXISTS `tb_emp`;
CREATE TABLE `tb_emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `deptid` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_tb_emp_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `tb_emp`(name,deptid) VALUES ('jack', '1');
INSERT INTO `tb_emp`(name,deptid) VALUES ('tom', '1');
INSERT INTO `tb_emp`(name,deptid) VALUES ('tonny', '1');
INSERT INTO `tb_emp`(name,deptid) VALUES ('mary', '2');
INSERT INTO `tb_emp`(name,deptid) VALUES ('rose', '2');
INSERT INTO `tb_emp`(name,deptid) VALUES ('luffy', '3');
INSERT INTO `tb_emp`(name,deptid) VALUES ('outman', '4');

複製代碼

b)tb_dept表。

複製代碼

DROP TABLE IF EXISTS `tb_dept`;
CREATE TABLE `tb_dept` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `deptname` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `tb_dept`(deptname) VALUES ('研發');
INSERT INTO `tb_dept`(deptname) VALUES ('測試');
INSERT INTO `tb_dept`(deptname) VALUES ('運維');
INSERT INTO `tb_dept`(deptname) VALUES ('經理');

複製代碼

#1.system

表只有一行記錄(等於系統表),是const的特例類型,平時不會出現,可以忽略不計。

但是筆者發現在MySQL5.7.22時,不會出現該字段值,只能出現const,但是在MySQL5.7版本以下可以出現該情況。猜測MySQL5.7版本是不是進行了優化,因爲system官網的解釋:

5.5.48:

5.7.22:

注:兩個引擎的執行信息不一樣,5.5.48執行過程中產生了臨時表(DERIVED),5.7.22爲簡單查詢。

#2.const

表示通過一次索引就找到了結果,常出現於primary key或unique索引。因爲只匹配一行數據,所以查詢非常快。如將主鍵置於where條件中,MySQL就能將查詢轉換爲一個常量。

注:對於system和const可能實際意義並不是很大,因爲單表單行查詢本來就快,意義不大。

#3.eq_ref

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

注:經理只有一人,進行了tb_dept的主鍵掃描。

#4.ref

非唯一性索引掃描,返回匹配某個單獨值的所有行。本質上也是一種索引訪問,返回匹配某值(某條件)的多行值,屬於查找和掃描的混合體。

由於是非唯一性索引掃描,所以對tb_emp表的deptid字段創建索引:

create index idx_tb_emp_deptid on tb_emp(deptid);

#5.range

只檢索給定範圍的行,使用一個索引來檢索行,可以在key列中查看使用的索引,一般出現在where語句的條件中,如使用between、>、<、in等查詢。

這種索引的範圍掃描比全表掃描要好,因爲索引的開始點和結束點都固定,不用掃描全索引。

雖然我們爲deptid字段創建了索引並在where中使用了between等,但在如下情況type仍爲ALL。

對比兩圖,可以看到使用deptid和id進行操作,其type的值一個是ALL也就是進行了全表掃描,一個是range進行了指定索引範圍值檢索。可能原因deptid並不是唯一索引。

對於以上問題,需要具體問題具體分析,並不能一概而論。

#6.index

全索引掃描,index和ALL的區別:index只遍歷索引樹,通常比ALL快,因爲索引文件通常比數據文件小。雖說index和ALL都是全表掃描,但是index是從索引中讀取,ALL是從磁盤中讀取。

#7.ALL

全表掃描。

注:一般來說,需保證查詢至少達到range級別,最好能達到ref。

⑥possible_keys和key、key_len

possible_keys:顯示可能應用在表中的索引,可能一個或多個。查詢涉及到的字段若存在索引,則該索引將被列出,但不一定被查詢實際使用。

key:實際中使用的索引,如爲NULL,則表示未使用索引。若查詢中使用了覆蓋索引,則該索引和查詢的select字段重疊。

key_len:表示索引中所使用的字節數,可通過該列計算查詢中使用的索引長度。在不損失精確性的情況下,長度越短越好。key_len顯示的值爲索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,並不是通過表內檢索出的。

簡單理解:possible_keys表示理論上可能用到的索引,key表示實際中使用的索引。

possible_keys爲NULL表示可能未用到索引,但key=idx_deptid表示在實際查詢的過程中進行了索引的全掃描。

通過下面的例子來理解key_len,首先爲name字段創建索引:

create index idx_name on tb_emp(name);

注:在使用索引查詢時,當條件越精確,key_len的長度可能會越長,所以在不影響結果的情況下,key_len的值越短越好。

⑦ref

顯示關聯的字段。如果使用常數等值查詢,則顯示const,如果是連接查詢,則會顯示關聯的字段。

注:由於id相同,因此從上到下執行:

#1.tb_emp表爲非唯一性索引掃描,實際使用的索引列爲idx_name,由於tb_emp.name='rose'爲一個常量,所以ref=const。

#2.tb_dept爲唯一索引掃描,從sql語句可以看出,實際使用了PRIMARY主鍵索引,ref=db01.tb_emp.deptid表示關聯了db01數據庫中tb_emp表的deptid字段。

⑧rows

根據表統計信息及索引選用情況大致估算出找到所需記錄所要讀取的行數。當然該值越小越好。

⑨filtered

百分比值,表示存儲引擎返回的數據經過濾後,剩下多少滿足查詢條件記錄數量的比例。

⑩Extra

顯示十分重要的額外信息。其取值有以下幾個:

#1.Using filesort

Using filesort表明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。

mysql中無法利用索引完成的排序操作稱爲“文件排序”。

出現Using filesort就非常危險了,在數據量非常大的時候幾乎“九死一生”。出現Using filesort儘快優化sql語句。

deptname字段未建索引的情況。

爲deptname字段創建索引後。

#2.Using temporary

使用了臨時表保存中間結果,常見於排序order by和分組查詢group by。非常危險,“十死無生”,急需優化。

將tb_emp中name的索引先刪除,出現如下圖結果,非常爛,Using filesort和Using temporary,“十死無生”。

爲name字段創建索引後。

#3.Using index

表明相應的select操作中使用了覆蓋索引,避免訪問表的額外數據行,效率不錯。

如果同時出現了Using where,表明索引被用來執行索引鍵值的查找。(where deptid=1)

如果沒有同時出現Using where,表明索引用來讀取數據而非執行查找動作。

刪除tb_emp表中name和deptid字段的單獨索引,創建複合索引。

從這裏給出覆蓋索引的定義:select的數據列只從索引中就能取得數據,不必讀取數據行。通過上面的例子理解:創建了(name,deptid)的複合索引,查詢的時候也使用複合索引或部分,這就形成了覆蓋索引。簡記:查詢使用複合索引,並且查詢的列就是索引列,不能多,個數需對應。

使用優先級Using index>Using filesort(九死一生)>Using temporary(十死無生)。也就說出現後面兩項表明sql語句是非常爛的,急需優化!!!

總結

explain(執行計劃)包含的信息十分的豐富,着重關注以下幾個字段信息。

①id,select子句或表執行順序,id相同,從上到下執行,id不同,id值越大,執行優先級越高。

②type,type主要取值及其表示sql的好壞程度(由好到差排序):system>const>eq_ref>ref>range>index>ALL。保證range,最好到ref。

③key,實際被使用的索引列。

④ref,關聯的字段,常量等值查詢,顯示爲const,如果爲連接查詢,顯示關聯的字段。

⑤Extra,額外信息,使用優先級Using index>Using filesort(九死一生)>Using temporary(十死無生)。

着重關注上述五個字段信息,對日常生產過程中調優十分有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章