Explain 介紹
在分析查詢性能時,考慮EXPLAIN關鍵字同樣很管用。EXPLAIN關鍵字一般放在SELECT查詢語句的前面,用於描述MySQL如何執行查詢操作、以及MySQL成功返回結果集需要執行的行數。explain 可以幫助我們分析 select 語句,讓我們知道查詢效率低下的原因,從而改進我們查詢,讓查詢優化器能夠更好的工作,可以幫助選擇更好的索引和寫出更優化的查詢語句。
執行計划用來顯示對應語句在MySQL中是如何執行的。 Explain語句對select,delete,update,insert,replace語句有效。
id列:
表示執行順序,值越大則優先級越高;值相同則從上而下執行
select_type列常見的有:
simple:表示不需要union操作或者不包含子查詢的簡單select查詢。有連接查詢時,外層的查詢為simple,且只有一個 primary:一個需要union操作或者含有子查詢的select,位於最外層的單位查詢的select_type即為primary。 且只有一個 union:union連接的兩個select查詢,第一個查詢是dervied派生表,除了第一個表外,第二個以后的表 select_type都是union dependent union:與union一樣,出現在union 或union all語句中,但是這個查詢要受到外部查詢的影響 union result:包含union的結果集,在union和union all語句中,因為它不需要參與查詢,所以id字段為null subquery:除了from字句中包含的子查詢外,其他地方出現的子查詢都可能是subquery dependent subquery:與dependent union類似,表示這個subquery的查詢要受到外部表查詢的影響 derived:from字句中出現的子查詢,也叫做派生表,其他數據庫中可能叫做內聯視圖或嵌select
table列
顯示的查詢表名,如果查詢使用了別名,那么這里顯示的是別名,如果不涉及對數據表的操作,那么這顯示為null,如果顯示為尖括號括起來的<derived N>
就表示這個是臨時表,后邊的N就是執行計划 中的id,表示結果來自於這個查詢產生。如果是尖括號括起來<union M,N>,與<derived N>
類似, 也是一個臨時表,表示這個結果來自於union查詢的id為M,N的結果集
Type列
:表示訪問類型,性能從低到高依次是:ALL->index->range->ref->eq_ref->const->system
- ALL:Full Table Scan, MySQL將遍歷全表以找到匹配的行
- index:Full Index Scan(覆蓋索引)index與ALL區別為index類型只遍歷索引樹,例如count(*)
- range:索引范圍掃描,對索引的掃描開始於某一點,返回匹配值域的行,常見於between、and ,in, <、 >等的查詢
- unique_subquery:用於where中的in形式子查詢,子查詢返回不重復值唯一值
- index_subquery:用於in形式子查詢使用到了輔助索引或者in常數列表,子查詢可能返回重復值,可以使用索引將子查詢去重
- ref:非唯一性索引掃描,等值匹配,可能有多行命中。返回匹配某個單獨值的所有行。常見於使用非唯一索引和唯一索引的非唯一前綴進行的查找
- eq_ref:唯一性索引掃描,PK或者unique上的join查詢。對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描的多表鏈接操作中
- system最快:不進行磁盤IO。當MySQL對查詢某部分進行優化,並轉換為一個常量時,使用這些類型訪問。如將主鍵置於where列表中,MySQL就能將該 查詢轉換為一個常量。 System為表中只有一行數據或者是空表,且只能用於myisam和memory表。如果是Innodb引擎表, type列在這個情況通常都是all或者index
- const:使用唯一索引或者主鍵上的等值查詢,返回記錄一定是1行記錄的等值where條件時,通常type是const。其他數據庫也叫做唯一索引掃描
- NULL:MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引
possible_keys列
表示MySQL能使用哪個索引在表中找到行,查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用
Key列:
表示MySQL在查詢中實際使用的索引
,若沒有使用索引,顯示為NULL
key_len列:
表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度
Ref列:
如果是使用的常數等值查詢,這里會顯示const,如果是連接查詢,被驅動表的執行計划這里會顯示驅動表的關聯字段,如果是條件使用了表達式或者函數,或者條件列發生了內部隱式轉換,這里可能顯示為func
Rows列:
表示MySQL根據表統計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數
,值越大性能越差
Extra列:
包含不適合在其他列中顯示但十分重要的額外信息
Using index:該值表示相應的select操作中使用了覆蓋索引(Covering Index) Using where:表示MySQL服務器在存儲引擎收到(使用索引)記錄后進行“后過濾” Using temporary:表示MySQL需要使用臨時表來存儲結果集,常見於排序和分組查詢 Using filesort: MySQL中無法利用索引完成的排序操作稱為“文件排序”,常見於order by和group by語句中
SQL 優化原則
- 盡可能消除全表掃描,除非表數據量是在萬條一下
-
增加適當的索引能提高查詢的速度,但增加索引需要遵循一定的基本規則:
a. 加在where條件上
b. 加在表之間join的鍵值上
c. 如果查詢范圍是少量字段,可以考慮增加覆蓋索引(僅走索引)
d. 有多個查詢條件時,考慮增加復合索引,並把最常使用的字段放在索引前面
e. 不要將索引加在區別率不高的字段上
f . 字段上增加函數,則字段上的索引用不了,需考慮改變寫法 -
去掉不影響查詢結果的表
慢查詢日志
開啟慢查詢日志,分日里面執行時間很長語句 , 可以針對性的對常用語句進行建立索引
開啟方法my.cnf:
slow_query_log= on #開啟 slow_query_log_file = /path/mysql-slow.log # 慢查詢文件存放位置 long_query_time= 2 #2秒以上的語句被記錄
慢查詢日志並不是只是記錄的查出select 語句 ,dml 對數據語句都會記錄
SQL 優化測試
創建一個有索引的表
create table students ( sid int, sname varchar(64), gender int, dept_id int, primary key(sid) );
創建一個什么索引都沒有的表
create table students_noindex ( sid int, sname varchar(64), gender int, dept_id int );
利用存儲過程, 分別給有索引的表和沒有索引的表創建測試數據
# 有索引的 表 delimiter // CREATE PROCEDURE `proc_students`() Begin Declare n int default 1; while n<=500000 do Insert into students values(n, concat('zhang san',n),floor(1+rand()*2),floor(1+rand()*4)); Set n=n+1; End while; End; // delimiter ;
# 沒有索引的 表 delimiter // CREATE PROCEDURE `proc_students_noindex`() Begin Declare n int default 1; while n<=500000 do Insert into students_noindex values(n, concat('zhang san',n),floor(1+rand()*2),floor(1+rand()*4)); Set n=n+1; End while; End; // delimiter ;
如果 表上所有字段都有索引的情況下,測試對插入性能的影響:
create index idx_sname on students(sname); create index idx_gender on students(gender);
看看兩個表students,students_noindex結構
分別在兩個表插入數據看時間消耗
set autocommit=0; call proc_students(); commit; call proc_students_noindex(); commit;
沒有索引的表插入數據更快
考慮性能消耗的情況
這是500000萬行的記錄插入,有索引的插入時間更久 ,沒有索引的插入更快
用時整體時間都比沒有索引的插入數據慢 , 反應情況來看是索引建的越多對SQL增刪改消耗的性能越大
,因為不僅會修改表數據,還會整理一些索引信息
如果是上億條的數據記錄插入,想想插入時間 , 還有大表數據遷移 在目標表都把索引給刪掉,插入數據完成的,在目標表統一建立索引
打開autocommit和關閉autocommit插入數據的區別
truncate table students; truncate table students_noindex; set autocommit=1; call proc_students();
插入數據中途可以在打開一個會話窗口看插入了多少數據
select count(*) from students;
自動提交開啟插入500000條記錄真的要花很長很長時間, 而自動提交關閉 幾十秒的時間都把500000行數據插入完了
是因為每條數據插入都會寫入磁盤 ,而關閉autocommit 是在插入完數據在統一把500000條記錄commit;寫入到磁盤
我在把原來沒有索引的students_noindex 數據插入回去
測試單表在沒有索引下全表掃描和走索引情況下的性能對比:
select 查詢加上sql_no_cache 查詢的時候不使用緩存 ,突出我的實驗結果
上面圖片很明顯是 走索引情況查詢速度更快
通過explain 看下
沒有索引走的全表掃描
測試通過區別度不高的字段(如gender)上查詢和全表查詢的性能對比:
create temporary table a select * from students where gender=1; create temporary table b select * from students_noindex where gender=1;
在區別度很低 (gender上有索引)查詢和全表查詢 性能上差不多
測試通過索引查詢表中絕大多數數據和全表查詢的性能對比:
select SQL_NO_CACHE count(*) from students where sid>1; # 類似全表查詢了 select SQL_NO_CACHE count(*) from students where sid>10000; # 查詢表的大多數數據
查詢時間是一樣的 。
使用查詢條件更可能小的約束過濾范圍
測試表鏈接關聯字段走索引和不走索引的性能對比:
create index idx_deptid on students(dept_id); explain select count(*) from students a inner join dept b on a.dept_id=b.id; # dept_id字段有索引 explain select count(*) from students_noindex a inner join dept b on a.dept_id=b.id; #students_noindex 的表沒有任何索引 select SQL_NO_CACHE count(*) from students a inner join dept b on a.dept_id=b.id; select SQL_NO_CACHE count(*) from students_noindex a inner join dept b on a.dept_id=b.id
在關聯字段上加了索引 查詢時間只用了0.07s 用時 比沒有走索引的快了很多很多
總結:
優化手段不只一種 ,要根據實際情況,很多情況都是以最低成本去處理, 例如
有可能加索引就能解決, 有可能解決不了,語句的寫法的可能有問題(例如語句有函數,表達式),也有可能去改表的結構(例如增加冗余字段),有可能數據庫瓶頸問題, 網絡情況問題,服務器性能IO 問題,等等。