官方文檔:Understanding the Query Execution Plan
SQL優化的一般步驟:先查詢mysql數據庫運行狀況,然后定位慢查詢,再分析sql的執行過程,最后根據情況采取相應的優化措施。
一、定位慢查詢
1.使用show status查詢數據庫的運行狀況
//顯示數據庫運行狀態 SHOW STATUS //顯示數據庫運行總時間 SHOW STATUS LIKE 'uptime' //顯示連接的次數 SHOW STATUS LIKE 'connections' //顯示執行CRUD的次數 SHOW STATUS LIKE 'com_select' SHOW STATUS LIKE 'com_insert' SHOW STATUS LIKE 'com_update' SHOW STATUS LIKE 'com_delete'
2.定位慢查詢sql
我們可以通過mysql來記錄慢查詢,一旦有sql執行時間超過了設置的慢查詢時間,就會被記錄到慢查詢日志中。這樣我們就可以從慢查詢日志中定位慢查詢sql,然后進行分析優化。
查看慢查詢相關信息
//顯示慢查詢次數 SHOW STATUS LIKE 'slow_queries' //顯示慢查詢時間,默認為10s SHOW VARIABLES LIKE 'long_query_time'
mysql的慢查詢默認是關閉的,可以通過修改配置文件或通過命令來開啟。
①修改配置文件方式
Linux下修改my.cnf,Windows下修改my.ini。修改后需要重啟mysql才會生效。
#開啟慢查詢
slow-query-log=1
#慢查詢的文件路徑
slow_query_log_file="D:/Program Files/MySQL/Log/mysql-slow.log"
#慢查詢時間。默認為10秒
long_query_time=10
②命令方式
也可以使用命令來修改。
【session級別】 #開啟慢查詢 SET slow_query_log='ON'; #設置慢查詢日志存放位置 SET slow_query_log_file='/usr/local/mysql/data/slow.log'; #設置慢查詢時間 SET long_query_time=3 【global級別】 SET global slow_query_log='ON'; SET global slow_query_log_file='/usr/local/mysql/data/slow.log'; SET global long_query_time=3
3.分析慢查詢
在實際生產環境中,可能因為開發寫了不正確的SQL語句,索引優化的不好,或其他查詢操作而導致數據庫整體性能下降。我們只需要分析一下慢查詢日志就會知道問題出在哪。
//查看是否啟用慢日志記錄和狀態 show variables like "%slow%"

如果慢查詢日志中記錄內容較多,則可以使用Mysql自帶的慢查詢日志分析工具mysqldumpslow工具來對慢查詢日志進行分類匯總。該工具位於/mysql/bin目錄下。mysqldumpslow將會自動將文本完全一致但變量不同的SQL語句視為同一個語句進行統計,變量值用N來代替。
mysqldumpslow -s r -t 10 /data/dbdata/frem-slow.log

另外,show processlist也是一個常用命令。
官方文檔:13.7.5.29 SHOW PROCESSLIST Statement
show full processlist
show processlist 是顯示用戶正在運行的線程,需要注意的是,除了 root 用戶能看到所有正在運行的線程外,其他用戶都只能看到自己正在運行的線程,看不到其它用戶正在運行的線程。除非單獨個這個用戶賦予了PROCESS 權限。
可以參考:show processlist 詳解
二、使用explain分析sql執行過程
官方文檔:Optimizing Queries with EXPLAIN
mysql會將慢查詢記錄到慢查詢日志中,這時我們就可以針對這些慢查詢的sql進行分析和優化,需要用到explain命令。
explain [要分析的sql]
分析結果中有如下幾列:
+----+-------------+---------+------+---------------+------+---------+------+------+-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+-------+
下面介紹各列的含義。可以參考官方文檔說明:Explain Output Colums
id
表示select查詢序列號。id值越大,越優先執行。如果id相同,執行順序由上至下;

select_type
表示查詢操作的類型。主要用於區分普通查詢、子查詢、聯合查詢等幾種查詢情況。有這些取值:simple,primary,subquery,derived,union,union result
①simple:表示簡單查詢,只有一個select操作,即不使用連接和union。
#只有一個select操作,所以都是簡單查詢 select id from emp; select id from emp join dept on emp.dept_id=dept.id;
②primary:表示主查詢。子查詢語句中的最外層select,或union操作的第一個select。
#子查詢形式:第一個select操作為primary select * from app_school where id = (select id from app_school where id=100); #union形式:第一個select操作為primary select * from app_school where id=100 union select * from app_school where id=101;
③subquery:表示子查詢。子查詢語句中的內層select。
#第二個select操作為subquery select * from app_school where id = (select id from app_school where id=100);
④derived:表示FROM后跟着的select查詢,會被標記為derived(導出表/衍生表)。
#第二個select操作為derived select * from (select id from app_school) t;
⑤union:表示UNION操作后面的select查詢。
#第二個select操作為union select * from app_school where id=100 union select * from app_school where id=101;
⑥union result:表示獲取UNION最后結果的查詢。
#第一個select操作為primary #第二個select操作為union #獲取最終結果的操作為union result select * from app_school where id=100 union select * from app_school where id=101;

table
表示查詢用到的表。
type
表示找到匹配行用到的訪問類型。最為常見的類型有system,const,eq_ref,ref,range,index,All,按照性能從高到低順序如下:NULL-->system-->const-->eq-ref-->ref-->range-->index-->All 。一般來說,要讓查詢至少達到range級別,最好能達到ref級別。
①NULL:不用訪問表或索引,就可直接得出結果。
②system:該表是最多僅有一行的系統表(這是const類型的一個特例)。系統表中的數據通常已經加載到了內存中,所以不需要磁盤IO。
例子1:查詢系統表
例子2:內層嵌套(const)返回了一個臨時表,外層嵌套從臨時表中查詢,其掃描類型也是system,也不需要磁盤IO。

③const:最多只有一個匹配行,所以該行中的其它列的值可以當作常量來處理。例如,根據主鍵primary key或唯一索引unique index進行查詢。簡單地說const就是直接按主鍵或唯一鍵取值。例如在②中介紹system時的舉例中user表的訪問類型就是const,其通過主鍵來取值。

④eq_ref:使用唯一索引,對於每個索引鍵值,表中只有一條記錄匹配。簡單說,就是多表連接中使用primary key或unique index作為關聯條件。
注意const和eq_ref的區別:簡單地說是const是直接按主鍵或唯一鍵讀取,eq_ref用於聯表查詢的情況,按聯表的主鍵或唯一鍵聯合查詢。


⑤ref:使用非唯一索引,或唯一索引的前綴掃描,返回匹配某個單獨值的所有行(可能匹配多個行)。


ref還經常出現在join操作中

⑥ref_or_null:與ref類似,區別在於條件中包含對NULL的查詢。
⑦index_merge:索引合並優化。
⑧unique_subquery:in的后面是一個查詢主鍵字段的子查詢。
⑨index_subquery:與unique subquery類似,區別在於in的后面是查詢非唯一索引字段的子查詢。
⑩range:只檢索指定范圍的行,使用一個索引來選擇行。常見於<,<=,>,>=,between或者IN操作符。
key列顯示使用了哪個索引。key_len包含所使用索引的最長關鍵元素。

11.index:索引全掃描。遍歷整個索引來查詢匹配的行。

12.ALL:全表掃描,性能最差。

possible_keys和key
possible_keys表示查詢時可能使用到的索引,而key表示實際使用的索引
key_len
表示使用到的索引字段的長度
ref
表示該表的索引字段關聯了哪張表的哪個字段
rows
表示掃描行的數量
Extra
表示執行情況的說明和描述。包含不適合在其它列中顯示但對執行計划非常重要的額外信息。
可以參考:MySQL中explain執行計划中額外信息字段(Extra)詳解
記錄幾個重要的:
Using index :使用覆蓋索引的時候就會出現
Using where:在查找使用索引的情況下,需要回表去查詢所需的數據 表示Mysql將對storage engine提取的結果進行過濾,過濾條件字段無索引;
Using index condition:查找使用了索引,但是需要回表查詢數據 會先條件過濾索引,過濾完索引后找到所有符合索引條件的數據行,隨后用 WHERE 子句中的其他條件去過濾這些數據行;
Using index & using where:查找使用了索引,但是需要的數據都在索引列中能找到,所以不需要回表查詢數據
Using filesort:使用了文件排序。當查詢語句包含ORDER BY時,如果無法使用索引來完成排序,則需要進行額外的排序操作。
Using temporary:使用臨時表來保存中間結果,
三、使用show profile分析sql
官方文檔:SHOW PROFILE Statement和SHOW PROFILES Statement
有時僅通過explain分析執行計划並不能很快地定位SQL的問題,這時就可以選擇profile聯合分析。Mysql從5.0.37開始增加了對show profiles和show profile語句的支持。
默認profile是關閉的,可以通過set開啟session級別的profiling。
//查看是否支持profile SELECT @@have_profiling //開啟profile(session級別) set profiling=1;
①執行select語句
②show profiles,查詢該sql語句的Query ID.
③通過show profile for query語句能看到執行中線程的每個狀態和消耗的時間。
show profile for query [上面的query id]
四、通過trace分析優化器如何選擇執行計划
官方文檔:Chapter 8 Tracing the Optimizer
Mysql5.6提供了對sql的跟蹤trace,通過trace文件能夠進一步了解為什么優化器選擇A執行計划而不選擇B執行計划,幫助我們更好地理解優化器的行為。
下面是典型的使用方式:
# 開啟trace(默認關閉),設置json格式 SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on; //設置內存,避免解析過程中因為默認內存過小而不能夠完整顯示。 SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000; #執行sql SELECT ...; # your query here #檢查INFORMATION_SCHEMA.OPTIMIZER_TRACE就知道mysql是如何執行sql的了 SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; # 完成后,關閉trace SET optimizer_trace="enabled=off";
最后會輸出一個跟蹤文件。
