EXPLAIN 分析你的查詢語句或是結構的性能瓶頸。html
使用EXPLAIN 關鍵字能夠模擬優化器執行 SQL 語句,從而知道 MySQL 是如何處理你的 SQL 語句的。mysql
在平常工做中,咱們有時會碰到執行較慢的 SQL,此時咱們可使用 EXPLAIN 關聯字來執行 SQL,能夠查看到 SQL 語句有沒有用到索引,是否是全表掃描等等,這些均可以經過 EXPLIN 命令來查看,咱們能夠經過查看到的信息進行進一步的優化。sql
在SELECT語句以前增長 EXPLAIN 關鍵字,MySQL 會在查詢上設置一個標識,執行查詢時,會返回執行計劃的信息,而不是執行這條 SQL。bash
id = 主鍵、 name = 學生姓名、 update_time = 更新時間函數
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`name` varchar(45) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 插入數據
INSERT INTO `student` (`id`, `name`, `update_time`) VALUES (1,'a','2017-12-22 15:27:18'), (2,'b','2017-12-22 15:27:18'), (3,'c','2017-12-22 15:27:18');
複製代碼
id = 主鍵、 name = 課程名稱(普通索引)性能
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 插入數據
INSERT INTO `course` (`id`, `name`) VALUES (1,'Java'), (2,'Python'), (3,'JS');
複製代碼
id = 主鍵、 student_id = 學生表主鍵、 course_id = 課程表主鍵、 remark = 備註測試
其中 student_id 和 course_id 爲複合索引優化
DROP TABLE IF EXISTS `student_course`;
CREATE TABLE `student_course` (
`id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
`course_id` int(11) NOT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_student_course_id` (`student_id`,`course_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 插入數據
INSERT INTO `student_course` (`id`, `student_id`, `course_id`) VALUES (1,1,1),(2,1,2),(3,2,1);
複製代碼
到如今爲止,須要準備的表已經完成了,咱們先簡單使用下 EXPLAIN 看下都有哪些東西。spa
explain select * from actor;
複製代碼
執行上面這條語句,會獲得這麼一個結果集,稍後咱們詳細介紹每一個字段的含義。翻譯
在使用EXPLAIN時,在查詢中的每一個表都會輸出一行記錄(一條SQL中有幾個 SElECT 就會有幾條記錄)。
EXPLAIN 有兩個變種:
show warnings;
命令能夠獲得優化後的查詢語句,能夠獲得優化後的查詢語句,從而看出優化器優化了什麼。(這個能夠優化一些簡單的SQL, 稍微複雜一點的仍是本身動手吧 ヾ(=・ω・=)o )explain extended select * from student WHERE id = 1;
SHOW WARNINGS;
複製代碼
接下來咱們將展現 EXPLAIN 中每一個列的信息。
id 列的編號是 SELECT 的序列號,有幾個 SELECT 就有幾個 id,而且 id 的順序是按 SELECT 出現的順序增加的。 id 列越大執行優先級越高,id 相同則從上往下執行,id 爲 NULL 最後執行。
EXPLAIN SELECT (SELECT 1 FROM student LIMIT 1) FROM course;
複製代碼
EXPLAIN SELECT id FROM (SELECT id FROM student) AS stu;
複製代碼
注: 我使用的 5.7 的版本,在執行後,只是一條簡單的查詢,返回結果裏並無用到臨時表。
我在使用 5.6 版本測試的時候,執行該語句,獲得的結果集中,出現了臨時表。
在5.7以前的版本,會建立一個臨時表,而外部的 SELECT 查詢時用到了這個臨時表,例以下圖:
查看了官方文檔,官方文檔這樣寫道:使用了合併或實現來優化派生表和視圖引用 (原諒我個英語渣渣,直接谷歌翻譯了( ̄. ̄),英文好的大佬能夠直接看官網原文)。
優化器可使用兩種策略(也適用於視圖引用)處理派生表引用:
將派生表合併到外部查詢塊中
將派生表實現爲內部臨時表
例1:
SELECT * FROM (SELECT * FROM t1) AS derived_t1;
經過合併派生表 derived_t1,該查詢的執行相似於:
SELECT * FROM t1;
例2:
SELECT *
FROM t1 JOIN (SELECT t2.f1 FROM t2) AS derived_t2 ON t1.f2=derived_t2.f1
WHERE t1.f1 > 0;
經過合併派生表 derived_t2,該查詢的執行相似於:
SELECT t1.*, t2.f1
FROM t1 JOIN t2 ON t1.f2=t2.f1
WHERE t1.f1 > 0;
複製代碼
簡而言之就是:優化器以相同的方式處理派生表和視圖引用:它儘量避免沒必要要的實現,這樣能夠將條件從外部查詢推送到派生表,並生成更高效的執行計劃。
EXPLAIN SELECT 1 UNION ALL SELECT 1;
複製代碼
注: 和 FROM 子句中的子查詢 同樣,5.7以後的版本進行了優化。
select_type 表示對應行是簡單仍是複雜的查詢,若是是複雜的查詢,又是上述三種複雜查詢中的哪種。
簡單查詢
EXPLAIN SELECT * FROM student WHERE id = 2;
複製代碼
複雜查詢中最外層的 select
包含在 select 中的子查詢(不在 from 子句中)
MySQL會將結果存放在一個臨時表中,也稱爲派生表(derived的英文含義)
用這個例子來了解 primary、subquery 類型
EXPLAIN SELECT (SELECT 1 FROM student WHERE id = 1) FROM (SELECT * FROM course WHERE id = 1) c;
複製代碼
在 union 中的第二個和隨後的 select
從 union 臨時表檢索結果的 select
這一列表示 EXPLAIN 的一行正在訪問哪一個表。
這一列表示關聯類型或訪問類型,即 MySQL 決定如何查找表中的行,查找數據行記錄的大概範圍。
依次從最優到最差分別爲:system > const > eq_ref > ref > range > index > ALL,通常來講,得保證查詢達到 range 級別,最好達到 ref
MySQL 可以在優化階段分解查詢語句,在執行階段用不着再訪問表或索引。
例如:在索引列中選取最小值,能夠單獨查找索引來完成,不須要在執行時訪問表。
EXPLAIN SELECT MIN(id) FROM student;
複製代碼
MySQL 能對查詢的某部分進行優化並將其轉化成一個常量(能夠看show warnings 的結果)。
用於 primary key 或 unique key 的全部列與常數比較時,因此表最多有一個匹配行,讀取1次,速度比較快。
system 是 const 的特例,表裏只有一條元組匹配時爲 system
EXPLAIN extended SELECT * FROM (SELECT * FROM course WHERE id = 1) tmp;
複製代碼
show warnings;
複製代碼
primary key 或 unique key 索引的全部部分被鏈接使用 ,最多隻會返回一條符合條件的記錄。這多是在 const 以外最好的聯接類型了,簡單的 select 查詢不會出現這種 type。
EXPLAIN SELECT * FROM student_course LEFT JOIN course ON student_course.course_id = course.id;
複製代碼
相比 eq_ref,不使用惟一索引,而是使用普通索引或者惟一性索引的部分前綴,索引要和某個值相比較,可能會找到多個符合條件的行。
EXPLAIN SELECT * FROM course WHERE name = "Java";
複製代碼
EXPLAIN SELECT student_id FROM student LEFT JOIN student_course ON student.id = student_course.student_id;
複製代碼
範圍掃描一般出如今 IN()、BETWEEN、>、<、>= 等操做中。使用一個索引來檢索給定範圍的行。
EXPLAIN SELECT * FROM student WHERE id > 1;
複製代碼
掃描全表索引,這一般比ALL快一些。(index是從索引中讀取的,而all是從硬盤中讀取)
EXPLAIN SELECT * FROM course;
複製代碼
即全表掃描,意味着 MySQL 須要從頭至尾去查找所須要的行。一般狀況下這須要增長索引來進行優化了。
EXPLAIN SELECT * FROM student;
複製代碼
這一列顯示查詢可能使用哪些索引來查找。
使用 EXPLAIN 時可能出現 possible_keys 有列,而 key 顯示 NULL 的狀況,這種狀況是由於表中數據很少,MySQL 認爲索引對此查詢幫助不大,選擇了全表查詢。
若是該列是NULL,則沒有相關的索引。在這種狀況下,能夠經過檢查 WHERE 子句看是否能夠創造一個適當的索引來提升查詢性能,而後用 EXPLAIN 查看效果。
這一列顯示 MySQL 實際採用哪一個索引來優化對該表的訪問。
若是沒有使用索引,則該列是 NULL。
若是想強制 MySQL 使用或忽視 possible_keys 列中的索引,在查詢中使用 force index、ignore index。
這一列顯示了 MySQL 在索引裏使用的字節數,經過這個值能夠算出具體使用了索引中的哪些列。
key_len 計算規則以下:
字符串
類型 | 長度 |
---|---|
char(n) | n字節長度 |
varchar(n) | 2字節存儲字符串長度,若是是utf-8,則長度 3n + 2 |
數值類型
類型 | 長度 |
---|---|
tinyint | 1字節 |
tinyint | 2字節 |
int | 4字節 |
bigint | 8字節 |
時間類型
類型 | 長度 |
---|---|
date | 3字節 |
timestamp | 4字節 |
timestamp | 8字節 |
若是字段容許爲 NULL,須要1字節記錄是否爲 NULL
索引最大長度是768字節,當字符串過長時,MySQL 會作一個相似左前綴索引的處理,將前半部分的字符提取出來作索引。
這一列顯示了在 key 列記錄的索引中,表查找值所用到的列或常量,常見的有:const(常量),字段名(例:student.id)
這一列是mysql估計要讀取並檢測的行數,注意這個不是結果集裏的行數。
這一列展現的是額外信息。常見的重要值以下:
查詢的列被索引覆蓋,而且 WHERE 篩選條件是索引的前導列,是性能高的表現。通常是使用了覆蓋索引(索引包含了全部查詢的字段)。對於 InnoDB 來講,若是是輔助索引性能會有很多提升。
EXPLAIN SELECT student_id FROM student_course WHERE student_id = 1;
複製代碼
查詢的列未被索引覆蓋, WHERE 篩選條件非索引的前導列
EXPLAIN SELECT * FROM student WHERE NAME = 'Python';
複製代碼
查詢的列被索引覆蓋,而且 WHERE 篩選條件是索引列之一可是不是索引的前導列,意味着沒法直接經過索引查找來查詢到符合條件的數據
EXPLAIN SELECT course_id FROM student_course WHERE course_id = 1;
複製代碼
查詢的列未被索引覆蓋,而且 WHERE 篩選條件是索引的前導列,意味着用到了索引,可是部分字段未被索引覆蓋,必須經過 「回表」 來實現,不是純粹地用到了索引,也不是徹底沒用到索引
EXPLAIN SELECT * FROM student_course WHERE student_id = 1;
複製代碼
與 Using where 相似,查詢的列不徹底被索引覆蓋, WHERE 條件中是一個前導列的範圍
EXPLAIN SELECT * FROM student_course WHERE student_id > 1;
複製代碼
MySQL 須要建立一張臨時表來處理查詢。出現這種狀況通常是要進行優化的,首先是想到用索引來優化。
EXPLAIN SELECT DISTINCT name FROM student;
複製代碼
EXPLAIN SELECT DISTINCT name FROM course;
複製代碼
MySQL 會對結果使用一個外部索引排序,而不是按索引次序從表裏讀取行。此時 MySQL 會根據聯接類型瀏覽全部符合條件的記錄,並保存排序關鍵字和行指針,而後排序關鍵字並按順序檢索行信息。這種狀況下也是要考慮使用索引來優化的。
EXPLAIN SELECT * FROM student ORDER BY name;
複製代碼
EXPLAIN SELECT * FROM course ORDER BY name;
複製代碼
filtered 是一個半分比的值,rows * filtered / 100
能夠估算出將要和 EXPLAIN 中前一個表進行鏈接的行數(前一個表指 EXPLAIN 中的 id 值比當前表 id 值小的表)。
DROP TABLE IF EXISTS `employees`;
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('小明',22,'JAVA',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('李雷', 23,'Python',NOW());
INSERT INTO employees(name,age,position,hire_time) VALUES('韓梅梅',23,'JS',NOW());
複製代碼
如上,name
、age
、position
爲複合索引
EXPLAIN SELECT * FROM employees WHERE NAME= '小明';
複製代碼
EXPLAIN SELECT * FROM employees WHERE name= '小明' AND age = 22;
複製代碼
EXPLAIN SELECT * FROM employees WHERE NAME= '小明' AND age = 22 AND POSITION ='JAVA';
複製代碼
如上, 三條 SQL 都用到了索引
EXPLAIN SELECT * FROM employees WHERE NAME = '李磊';
複製代碼
如圖,能夠看到用到了索引。
EXPLAIN SELECT * FROM employees WHERE POSITION = 'Python';
複製代碼
如圖,能夠看到沒有用索引。
EXPLAIN SELECT * FROM employees WHERE age = 23 AND POSITION ='Python';
複製代碼
如圖,能夠看到也沒有用索引。
總結: 若是索引了多列,要遵照最左前綴法則。指的是查詢從索引的最左前列開始而且不跳過索引中的列。
例如 計算、函數、(自動or手動)類型轉換等,會致使索引失效而轉向全表掃描
EXPLAIN SELECT * FROM employees WHERE left(NAME,1) = '韓';
複製代碼
如圖,能夠看到沒有用索引。
EXPLAIN SELECT * FROM employees WHERE name= '小明' AND age = 22 AND position ='JAVA';
經過上邊實踐,知道該語句用到了索引
複製代碼
EXPLAIN SELECT * FROM employees WHERE name= '小明' AND age > 22 AND position ='JAVA';
複製代碼
咱們能夠計算下 key_len 長度,能夠得知 索引只用到了 name
和 age
, position
沒用用到索引,因此在複合索引中使用了範圍條件右邊的列索引會失效。
只訪問索引的查詢(索引列包含查詢列),減小 SELECT * 語句
EXPLAIN SELECT * FROM employees WHERE name= '小明' AND age = 22 AND POSITION ='Java';
複製代碼
EXPLAIN SELECT NAME, age, POSITION FROM employees WHERE NAME= '小明' AND age = 22 AND POSITION ='Java';
複製代碼
能夠看到,從 NULL 變成了 Using index
EXPLAIN SELECT * FROM employees WHERE NAME != '小明'
複製代碼
EXPLAIN SELECT * FROM employees WHERE name IS NOT NULL
複製代碼
EXPLAIN SELECT * FROM employees WHERE name LIKE '%李'
複製代碼
EXPLAIN SELECT * FROM employees WHERE name LIKE '李%'
複製代碼
EXPLAIN SELECT NAME, age, position FROM employees WHERE name LIKE '%李'
複製代碼
注:當覆蓋索引指向的字段是 varchar(380) 及 380 以上的字段時,覆蓋索引會失效!
EXPLAIN SELECT * FROM employees WHERE NAME = 1000;
複製代碼
EXPLAIN SELECT * FROM employees WHERE name = '李磊' OR NAME = '韓梅梅';
複製代碼
假設 a、b、c 爲複合索引
WHERE 語句 | 索引使用狀況 |
---|---|
WHERE a = '小明' | 使用到 a |
WHERE a = '小明' AND b = '李磊' | 使用到 a 、b |
WHERE a = '小明' AND b = '李磊' AND c = '韓梅梅' | 使用到 a、b、c |
WHERE b = '李磊' 或者 WHERE b = '李磊' AND c = '韓梅梅' 或者 WHERE c = '韓梅梅' | 沒有用到 |
WHERE a = '小明' AND c = '韓梅梅' | a 用到了,c 沒有用到,由於 b 中間斷了 |
WHERE a = '小明' AND b > '李磊' AND c = '韓梅梅' | a、b 用到了,c 不能用在範圍後 |
WHERE a = '小明' AND b = '李磊%' AND c = '韓梅梅' | 使用到 a、b、c |
WHERE a = '小明' AND b = '%李磊' AND c = '韓梅梅' | 只用到 a |
WHERE a = '小明' AND b = '%李磊%' AND c = '韓梅梅' | 只用到 a |
WHERE a = '小明' AND b = '李%磊%' AND c = '韓梅梅' | 使用到 a、b、c |