1. 程式人生 > >MySQL的執行計劃和索引詳解

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計算規則如下

字串

char(n):n位元組長度

varchar(n):2位元組儲存字串長度,如果是utf-8,則長度 3n + 2

數值型別

tinyint:1位元組

smallint:2位元組

int:4位元組

bigint:8位元組  

時間型別 

date:3位元組

timestamp:4位元組

datetime:8位元組

如果欄位允許為 NULL,需要1位元組記錄是否為 NULL

索引最大長度是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'%字串%'索引不被使用的方法?

a)使用覆蓋索引,查詢欄位必須是建立覆蓋索引欄位

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% 相當於範圍