MySQL的執行計劃和索引詳解
使用explain關鍵字可以模擬優化器執行sql語句,從而知道mysql是如何處理sql語句的,分析你的查詢語句或者是結構效能。
我們通過幾張表來使用explain的例子:
在select語句之前增加explain關鍵字,MySQL會在查詢的基礎上設定一個標記,執行查詢時,會返回執行計劃的資訊,而不是執行這條sql語句(如果from中包含子查詢,仍會執行該子查詢的,將結果放入臨時表中)
使用的的表
1.actor表(不設定索引,儲存引擎是InnoDB)
2.film表(設定name索引(輔助索引),儲存引擎是InnoDB)
3.film-actor表(設定聯合輔助索引(film_id,actor_id))
使用的表我們建立完畢了,那接下來就讓我們熟悉一下explain的始終和返回執行計劃中欄位的含義
mysql>EXPLAIN SELECT (SELECT 1 from actor ) from film;
在查詢中的每一個表會輸出一行,如果有兩個表通過join連線查詢,那麼會輸出兩行,表的意思相當廣泛:可以是子查詢,一個union結果等。
explain有兩個輔助寫法:
1)explain extended:會在explain的基礎上額外提供一些查詢的優化的資訊,緊隨其後的通過show warnings 命令可以的到優化後的查詢語句,從而看出優化器優化了什麼,二外有filtered列,是一個半分比的值,rows*filtered/100可以估算出來將要和explain中前一個表進行連線的行數(前一個表指的是explain中的id值比當前id值小的表)
mysql>EXPLAIN EXTENDED SELECT * from film where id = 1;
mysql>show WARNINGS;(Note 得到優化後的語句)
2)explain partitions :相比explain多了個partition欄位,如果查詢是基於分表的的話,會顯示查詢將訪問的分割槽
那接下來我們介紹一下explain中每個列的資訊
1.id列
id列的編號是select的序列號,有幾個select就有幾個id,並且id的順序是按select出現的順序增長的。MySQL將 select查詢分為簡單查詢(SIMPLE)和複雜查詢(PRIMARY)。
複雜查詢分為三類:簡單子查詢、派生表(from語句中的子查詢) 、union查詢。
id列越大執行優先順序越高,id相同則從上往下執行,id為NULL最後執行1)簡單子查詢
1)簡單子查詢
mysql>EXPLAIN SELECT (SELECT 1 FROM actor LIMIT 1) FROM film;
2)from字句中的子查詢(在mysql5.7的版中沒有派生表的返回,具體原因不明,可能是5.7的優化了)
mysql>EXPLAIN SELECT id FROM (SELECT id FROM film) f;
這個查詢執行時有個臨時表別名為f,外部的select查詢就是引用了這個臨時表
3)union查詢
mysql>EXPLAIN select 1 from film union all select 1 from actor;
union 結果總是放在一個匿名的臨時表中,臨時表不在sql中出現,因此他的id是null
2.select_type列
select_type表示對應行是簡單的還是複雜的查詢,如果是複雜的查詢,又是上述三種哪一種
1)simple:簡單查詢,查詢不包含子查詢和union
mysql>explain select * from film where id =1;
2)primary:複雜查詢中的最外層select
3)subquery:包含select中的子查詢(不在from子句中)
4)derived:包含在from子句中的子查詢,mysql會將結果存放在一個臨時表中,也稱派生表(derived的英文義)
用這個例子來解析primary,subquery,derived型別
mysql>explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;
5)union :在union中的第二和隨後的select
6)union result:從union臨時表檢索結果的select
用這個例子來了解union和union result 型別:
mysql> explain select 1 from film union select 1 from film ;
3.table 列
這一列表示explain的一行正在訪問哪個表。
當from子句中有子查詢的時候,table列是<derivenN>格式,表示當前查詢依賴id=N的查詢,於是先執行id=N的查詢。
當有union時,union result的table列的值為<union1,2>,1和2表示參與的select行id
4.type列
這一列表示關聯型別和訪問型別,即MySQL決定如何查詢表中的行,查詢的資料記錄的大概範圍。
依次從最優到最差分別為:system>const>eq_ref>ref>range>index>all(常用的)
一般來說,得到保證查詢打到range級別,最好打到ref級別
NULL:MySQL能夠在優化階段分解查詢語句,在執行階段用不著再訪問表或索引。例如再索引列中選區最小值,可以單獨擦查詢索引來完成,不需要再執行時訪問表。
mysql>explain select min(id) from film;
注意:mysql中主鍵就是索引,mysql會幫主鍵主動建立索引的
const,system:mysql能對查詢的某部分進行優化並將其轉化成一個常量(可以看著show warnings的結果)。用於primary_key和union key的所有列和常數比較的時候,所以表最多一個匹配行,讀一次,速度比較快,system時const的特例,表中只有一條元組資料匹配就是system
mysql>explain extended select * from (select * from film where id = 1) f;
注意:const就是查詢條件時主鍵或者唯一性約束的,查詢出來的就只有一條資料的,而system查詢的表就只有一條資料的。
mysql>show warnings;(優化後的sql)
eq_ref:primary_key或者union key索引的所有部分被連線使用,最多返回一條符合條件的記錄,這可能再const之外最好的連線型別了,簡單的select查詢就不會出現這種型別
mysql>explain select * from film_actor left join film on film_id = film.id ;
注意:film_actor也是使用索引查詢的原因時覆蓋索引(後面有說)
ref相比eq. ret,不使用唯一索引,而是使用普通索引或者唯一性索引的部分字首,索引要和某個值相比較,可能會找到多個符合條件的行。
1.簡單select查詢,name是普通索引(非唯一索引)
mysql>explain select * from film where name = "film1";
2.關聯表查詢,idx_ film_ actor_ id是film_ 1d和actor_ id的聯合索引,這裡使用到了film_ actor的左邊字首film_id部分。
mysql> explain select * from film LEFT JOIN film_actor on film.id = film_actor.film_id;
range:範圍掃描通常出現在in(), between,> <, >=等操作中。使用一個索引來檢索給定範圍的行。
mysql>explain select * from actor WHERE id > 1
index:掃描全表索引,這通常比all快一些,(index是從索引中讀取的,而all是從硬碟中讀取)
mysql>explain select * from film ;
ALL:即全表掃描,意味著mysql需要從頭到尾去查詢所需要的好行,通常情況下這需要增加索引來進行優化
mysql>explain select * from actor;
5.possible_keys列
這一列顯示查詢可能使用哪些索引來查詢。
explain時可能出現posible. keys有列,而key顯示NULL的情況,這種情況是因為表中資料不多, mysq認為索引對此查詢幫助不大,選擇了全表查詢。
如果該列是NULL,則沒有相關的素引,在這種情況下,可以通過檢查where子句看是否可以創造一個適當的索引來提高查詢效能,然後用explain檢視效果。
6.key列
這一列是示mysq實際採用索引來優化対該表的訪問
如果沒有使用索引,則垓列是NULL.如果想強制mysq使用或忽視possible. keys列中的索引,在査洵中使用force index. ignore index。
7.key_len列
這一列顯示了mysql在索引裡使用的位元組數,通過這個值可以算出具體使用了索引中的哪些列。
舉例來說,film_actor的聯合索引 idx_film_actor_id 由 film_id 和 actor_id 兩個int列組成,並且每個int是4位元組。通過結果中的key_len=4可推斷出查詢使用了第一個列:film_id列來執行索引查詢。
mysql>explain select * from film_actor where film_id = 2;
key_len計算規則如下
字串
varchar(n):2位元組儲存字串長度,如果是utf-8,則長度 3n + 2
索引最大長度是768位元組,當字串過長時,mysql會做一個類似左字首索引的處理,將前半部分的字元提取出來做索引。
8. ref列
這一列顯示了在key列記錄的索引中,表查詢值所用到的列或常量,常見的有: const (常量) ,欄位名(例: film.id)
9. rows列
這一列是mysq估計要讀取並檢測的行數,注意這個不是結果集裡的行數。
10. Extra列
這一列展示的是額外資訊。常見的重要值如下:
Using index: 查詢的列被索引覆蓋,並且where篩選條件是索引的前導列,是效能高的表現。一般是使用了覆蓋索引索引包含了所有查詢的欄位)。對於innodb來說,如果是輔助索引效能會有不少提高.
mysql>explain select film_id from film_actor where film_id = 1;
Using where : 查詢的列未被索引覆蓋 where篩選條件非索引的前導列
mysql>explain select * from actor where name = 'a';
Using where Using index: 查詢的列被索引覆蓋,並且where篩選條件是索引列之一但是不是索引的前導列,意味著無法直接通過索引查詢來查詢到符合條件的資料
mysql> explain select film_id from film_actor where actor_id = 1;
NULL:查詢的列未被索引覆蓋,並且where篩選條件是索引的前導列,意味著用到了索引,但是部分欄位未被索引覆蓋,必須通過“回表“來實現,不是純粹地用到了索引,也不是完全沒用到索引.
Using temporary: mysql需要建立一張臨時表來處理査洵. 出現這種情況一般是要迸行優化的, 首先是想到用索引來優化.
1. actor.name沒有索引,此時建立了張臨時表來dstinct.
mysql>explain select distinct name from actor;
2.film.name 建立了idx_name索引,此時查詢時extra時using index,沒有使用臨時表
mysql>explain select distinct name from film;
Using filesort: mysql會對結果使用一個外部索引排序,而不是按索引次序從表裡讀取行。此時mysql會根據連線型別瀏覽所有符合條件的記錄,井儲存排序關鍵字和指標,然後排序關鍵字並按順序檢索行資訊。這種情況下一般也是要考慮使用索引來優化的。
1. actor .name未建立索引,會瀏覽actor整個表, 儲存排序關鍵字name和對那個的id,然後排序name並檢索記錄
mysql>explain select * from actor order by name;
2.film.name 建立了idx_name索引,此時查詢時extra時using index
索引最佳實踐
使用的表
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',
`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',
PRIMARY KEY (`id`),
KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';
INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
最佳實踐
1. 全值匹配
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
2.最佳左字首法則
如果索引了多列,要遵守最左字首法則。指的是查詢從索引的最左前列開始並且不跳過索引中的列。
EXPLAIN SELECT * FROM employees WHERE age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
3.不在索引列上做任何操作(計算、函式、(自動or手動)型別轉換),會導致索引失效而轉向全表掃描
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';
4.儲存引擎不能使用索引中範圍條件右邊的列
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';
5.儘量使用覆蓋索引(只訪問索引的查詢(索引列包含查詢列)),減少select *語句
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
6.mysql在使用不等於(!=或者<>)的時候無法使用索引會導致全表掃描
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei'
7.is null,is not null 也無法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
8.like以萬用字元開頭('$abc...')mysql索引失效會變成全表掃描操作
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
問題:解決like'%字串%'索引不被使用的方法?
EXPLAIN SELECT name,age,position FROM employees WHERE name like '%Lei%';
b)當覆蓋索引指向的欄位是varchar(380)及380以上的欄位時,覆蓋索引會失效!
9.字串不加單引號索引失效
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
10.少用or,用它連線時很多情況下索引會失效
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';
總結:
like KK%相當於=常量,%KK和%KK% 相當於範圍