[MySql]explain用法及實踐


轉自:https://yq.aliyun.com/articles/51753

摘要: 寫在前面 explain對我們優化sql語句是非常有幫助的。可以通過explain+sql語句的方式分析當前sql語句。 例子 EXPLAIN SELECT dt,method,url FROM app_log WHERE id=11789 table 顯示這一行數據屬於哪張表,若在查詢中為select起了別名,則顯示別名。

寫在前面

explain對我們優化sql語句是非常有幫助的。可以通過explain+sql語句的方式分析當前sql語句。

例子

EXPLAIN SELECT dt,method,url FROM app_log WHERE id=11789

table

顯示這一行數據屬於哪張表,若在查詢中為select起了別名,則顯示別名。

EXPLAIN SELECT dt,method,url FROM app_log AS temp WHERE id=11789

type

在表里查到結果所用的方式。包括(性能有差——>高): All | index | range | ref | eq_ref | const,system | null |

all:全表掃描,MySQL 從頭到尾掃描整張表查找行。

EXPLAIN SELECT dt,method,url FROM app_log AS temp LIMIT 100

注意:這里雖然使用limit但並不能改變全表掃描。

index:按索引掃描表,雖然還是全表掃描,但優點是索引是有序的。

EXPLAIN SELECT id FROM app_log AS temp LIMIT 100

range:以范圍的方式掃描索引。比較運算符,以及in的type都是range。

EXPLAIN SELECT * FROM app_log AS temp WHERE id>100 LIMIT 199

ref:非唯一性索引訪問

EXPLAIN SELECT * FROM app_log AS temp  WHERE dt='2015-01-02' LIMIT 199

eq_ref:使用唯一性索引查找(主鍵或唯一索引)

EXPLAIN SELECT * FROM app_log JOIN app_details_log USING(id)

先全表掃描了app_details_log表,然后在對app_log進行eq_ref查找。因為app_log的id字段是主鍵。如果此時刪除app_log的id為主鍵,則都會進行全表掃描。

const:常量,在整個查詢過程中這個表最多只會有一條匹配的行,比如主鍵 id=1 就肯定只有一行,只需讀取一次表數據便能取得所需的結果,且表數據在分解執行計划時讀取。

EXPLAIN SELECT * FROM app_log WHERE id=11790

注意:system 是 const 類型的特例,當表只有一行時就會出現 system 。

null:在優化的過程已經得到結果,不再需要訪問表或索引。例如表中並不存在id=1000的記錄。

EXPLAIN SELECT * FROM app_log WHERE id=1000

possible_keys

可能被用到的索引。

EXPLAIN SELECT * FROM app_log  WHERE id>100 LIMIT 100 ;

Key

查詢過程中實際用到的索引,例子如上圖,實際用的索引列為主鍵列。

key_len

索引字段最大可能使用的長度。例如上圖中,Key_len:4,因為主鍵是int類型,長度為4.

ref

指出對key列所選擇的索引的查找方式,常見的有const,func,null,具體字段名。當key列為null,即不使用索引時,此值也為null.

rows

mysql估計需要掃描的行數,只是一個估算。

Extra

這個顯示其他的一些信息,但對優化sql也非常的重要。

using Index:此查詢使用了覆蓋索引(Convering Index),即通過索引就能返回結果,無需訪問表。弱沒顯示“Using Index”表示讀取了表數據。

EXPLAIN SELECT id FROM app_log;

因為 id 為主鍵索引,索引中直接包含了 id 的值,所以無需訪問表,直接查找索引就能返回結果。

using where:mysql從存儲引擎收到行后再進行“后過濾(Post-filter)”。后過濾:先讀取整行數據,再檢查慈航是否符合where的條件,符合就留下,不符合便丟棄。檢測是在讀取行后進行的,所以叫后過濾。

EXPLAIN SELECT id FROM app_log WHERE id>100 LIMIT 100;

using temporary:使用到臨時表,在使用臨時表的時候,Extra為這個值。

using filesort:若查詢所需的排序與使用的索引的排序一直,因為索引已排序,因此按索引的順序讀取結果返回,否則,在取到結果后,還需要按查詢所需的順序對結果進行排序,這時就會出現using filesort。

EXPLAIN SELECT id FROM app_log WHERE id>100 GROUP BY dt;

一個優化的例子

我需要對app_log的表按時間進行分組,顯示每個小時的人數。

通過上面你可以看到type一個為all,一個為range。為all的查詢需要23+s,而下面的則只需要0.3s。通過rows也能看出優化后,表掃描的行數變化。

 

假設有下面的 SELECT 語句,正打算用 EXPLAIN 來檢測:

EXPLAIN SELECT tt.TicketNumber, tt.TimeIn, tt.ProjectReference, tt.EstimatedShipDate, tt.ActualShipDate, tt.ClientID, tt.ServiceCodes, tt.RepetitiveID, tt.CurrentProcess, tt.CurrentDPPerson, tt.RecordVolume, tt.DPPrinted, et.COUNTRY, et_1.COUNTRY, do.CUSTNAME FROM tt, et, et AS et_1, do WHERE tt.SubmitTime IS NULL AND tt.ActualPC = et.EMPLOYID AND tt.AssignedPC = et_1.EMPLOYID AND tt.ClientID = do.CUSTNMBR;

在這個例子中,先做以下假設:

  • 要比較的字段定義如下:
    Table Column Column Type
    tt ActualPC CHAR(10)
    tt AssignedPC CHAR(10)
    tt ClientID CHAR(10)
    et EMPLOYID CHAR(15)
    do CUSTNMBR CHAR(15)
  • 數據表的索引如下:
    Table Index
    tt ActualPC
    tt AssignedPC
    tt ClientID
    et EMPLOYID (primary key)
    do CUSTNMBR (primary key)
  • tt.ActualPC 的值是不均勻分布的。

在任何優化措施未采取之前,經過 EXPLAIN 分析的結果顯示如下:

table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 do ALL PRIMARY NULL NULL NULL 2135 et_1 ALL PRIMARY NULL NULL NULL 74 tt ALL AssignedPC, NULL NULL NULL 3872 ClientID, ActualPC range checked for each record (key map: 35)

由於字段 type 的對於每個表值都是 ALL,這個結果意味着MySQL對所有的表做一個迪卡爾積;這就是說,每條記錄的組合。這將需要花很長的時間,因為需要掃描每個表總記錄數乘積的總和。在這情況下,它的積是 74 * 2135 * 74 * 3872 = 45,268,558,720 條記錄。

在這里有個問題是當字段定義一樣的時候,MySQL就可以在這些字段上更快的是用索引(對 ISAM 類型的表來說,除非字段定義完全一樣,否則不會使用索引)。在這個前提下,VARCHAR 和 CHAR是一樣的除非它們定義的長度不一致。由於 tt.ActualPC 定義為 CHAR(10)et.EMPLOYID 定義為 CHAR(15),二者長度不一致。
為了解決這個問題,需要用 ALTER TABLE 來加大 ActualPC 的長度從10到15個字符:

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

現在 tt.ActualPC 和 et.EMPLOYID 都是 VARCHAR(15)
了。再來執行一次 EXPLAIN 語句看看結果:

table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 Using ClientID, where ActualPC do ALL PRIMARY NULL NULL NULL 2135 range checked for each record (key map: 1) et_1 ALL PRIMARY NULL NULL NULL 74 range checked for each record (key map: 1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1

這還不夠,它還可以做的更好:現在 rows 值乘積已經少了74倍。這次查詢需要用2秒鍾。
第二個改變是消除在比較 tt.AssignedPC = et_1.EMPLOYID 和 tt.ClientID = do.CUSTNMBR 中字段的長度不一致問題:

mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15), -> MODIFY ClientID VARCHAR(15);

現在 EXPLAIN 的結果如下:

table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using ClientID, where ActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

這看起來已經是能做的最好的結果了。
遺留下來的問題是,MySQL默認地認為字段tt.ActualPC 的值是均勻分布的,然而表 tt 並非如此。幸好,我們可以很方便的讓MySQL分析索引的分布:

mysql> ANALYZE TABLE tt;

到此為止,表連接已經優化的很完美了,EXPLAIN 的結果如下:

table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 Using ClientID, where ActualPC et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1 et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

請注意,EXPLAIN 結果中的 rows 字段的值也是MySQL的連接優化程序大致猜測的,請檢查這個值跟真實值是否基本一致。如果不是,可以通過在 SELECT 語句中使用 STRAIGHT_JOIN 來取得更好的性能,同時可以試着在FROM
分句中用不同的次序列出各個表。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM