面試官說:工做這麼久了,應該知道sql執行計劃吧,講講Sql的執行計劃吧!
看了看面試官手臂上紋的大花臂和一串看不懂的韓文,吞了吞口水,暗示本身鎮定點,整理了一下思緒緩緩的對面試官說:我不會
面試官:。。。。,回去等通知吧
我:%^&%$!@#html
當咱們工做到了必定的年限以後,一些應該掌握的知識點,咱們是必須須要去了解的,好比今天面試官問的SQL執行計劃
當咱們執行一條SQL的時候,能夠直接對應的結果,可是你並不曉得,它會經歷多深遠黑暗的隧道,經過鏈接器、查詢緩存、分析器、優化器、執行器重重篩選,纔有可能展現到咱們面前,有時候當你等待N長時間,可是展示的倒是 timeout,這個時候想砸電腦的心都有了,不過當你看了今天的SQL執行計劃後,你不再用砸電腦了,看懂了這篇文章你就會知道這都不是事,讓咱們一塊兒來揭曉這裏面的奧妙java
在實際的應用場景中,爲了知道優化SQL語句的執行,須要查看SQL語句的具體執行過程,以加快SQL語句的執行效率。
一般會使用explain+SQL語句來模擬優化器執行SQL查詢語句,從而知道mysql是如何處理sql語句的。mysql
官網地址: https://dev.mysql.com/doc/refman/5.5/en/explain-output.html面試
首先咱們來下面的一條sql語句,其中會有id、select_type 、table 等等
這些列,這些就是咱們執行計劃中所包含的信息,咱們要弄明白的就是這些列是用來幹嗎的,以及每一個列可能存在多少個值。
explain select * from emp;
算法
列(Column) | 含義(Meaning) |
---|---|
id | The SELECT identifier(每一個select子句的標識id) |
select_type | The SELECT type(select語句的類型) |
table | The table for the output row(當前表名) |
partitions | The matching partitions (顯示查詢將訪問的分區,若是你的查詢是基於分區表) |
type | The join type(當前表內訪問方式) |
possible_keys | The possible indexes to choose(可能使用到的索引) |
key | The index actually chosen(通過優化器評估最終使用的索引) |
key_len | The length of the chosen key (使用到的索引長度) |
ref | The columns compared to the index(引用到的上一個表的列) |
rows | Estimate of rows to be examined (要獲得最終記錄索要掃描通過的記錄數) |
filtered | Percentage of rows filtered by table condition(存儲引擎返回的數據在server層過濾後,剩下知足查詢的記錄數量的比例) |
extra | Additional information (額外的信息說明) |
貼心的小農已經把sql複製出來了,只須要放到數據庫中執行便可,方便簡單快捷 ——牧小農
建表語句:sql
SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `DEPTNO` int NOT NULL, `DNAME` varchar(14) DEFAULT NULL, `LOC` varchar(13) DEFAULT NULL, PRIMARY KEY (`DEPTNO`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `dept` VALUES ('10', 'ACCOUNTING', 'NEW YORK'); INSERT INTO `dept` VALUES ('20', 'RESEARCH', 'DALLAS'); INSERT INTO `dept` VALUES ('30', 'SALES', 'CHICAGO'); INSERT INTO `dept` VALUES ('40', 'OPERATIONS', 'BOSTON'); DROP TABLE IF EXISTS `emp`; CREATE TABLE `emp` ( `EMPNO` int NOT NULL, `ENAME` varchar(10) DEFAULT NULL, `JOB` varchar(9) DEFAULT NULL, `MGR` int DEFAULT NULL, `HIREDATE` date DEFAULT NULL, `SAL` double(7,2) DEFAULT NULL, `COMM` double(7,2) DEFAULT NULL, `DEPTNO` int DEFAULT NULL, PRIMARY KEY (`EMPNO`), KEY `idx_job` (`JOB`), KEY `jdx_mgr` (`MGR`), KEY `jdx_3` (`DEPTNO`), KEY `idx_3` (`DEPTNO`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `emp` VALUES ('7369', 'SMITH', 'CLERK', '7902', '1980-12-17', '800.00', null, '20'); INSERT INTO `emp` VALUES ('7499', 'ALLEN', 'SALESMAN', '7698', '1981-02-20', '1600.00', '300.00', '30'); INSERT INTO `emp` VALUES ('7521', 'WARD', 'SALESMAN', '7698', '1981-02-22', '1250.00', '500.00', '30'); INSERT INTO `emp` VALUES ('7566', 'JONES', 'MANAGER', '7839', '1981-02-02', '2975.00', null, '20'); INSERT INTO `emp` VALUES ('7654', 'MARTIN', 'SALESMAN', '7698', '1981-09-28', '1250.00', '1400.00', '30'); INSERT INTO `emp` VALUES ('7698', 'BLAKE', 'MANAGER', '7839', '1981-01-05', '2850.00', null, '30'); INSERT INTO `emp` VALUES ('7782', 'CLARK', 'MANAGER', '7839', '1981-09-06', '2450.00', null, '10'); INSERT INTO `emp` VALUES ('7839', 'KING', 'PRESIDENT', null, '1981-11-17', '5000.00', null, '10'); INSERT INTO `emp` VALUES ('7844', 'TURNER', 'SALESMAN', '7698', '1981-09-08', '1500.00', '0.00', '30'); INSERT INTO `emp` VALUES ('7900', 'JAMES', 'CLERK', '7698', '1981-12-03', '950.00', null, '30'); INSERT INTO `emp` VALUES ('7902', 'FORD', 'ANALYST', '7566', '1981-12-03', '3000.00', null, '20'); INSERT INTO `emp` VALUES ('7934', 'MILLER', 'CLERK', '7782', '1982-01-23', '1300.00', null, '10'); DROP TABLE IF EXISTS `emp2`; CREATE TABLE `emp2` ( `id` int NOT NULL AUTO_INCREMENT, `empno` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; INSERT INTO `emp2` VALUES ('1', '111'); INSERT INTO `emp2` VALUES ('2', '222'); DROP TABLE IF EXISTS `salgrade`; CREATE TABLE `salgrade` ( `GRADE` int NOT NULL, `LOSAL` double DEFAULT NULL, `HISAL` double DEFAULT NULL, PRIMARY KEY (`GRADE`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `salgrade` VALUES ('1', '700', '1200'); INSERT INTO `salgrade` VALUES ('2', '1201', '1400'); INSERT INTO `salgrade` VALUES ('3', '1401', '2000'); INSERT INTO `salgrade` VALUES ('4', '2001', '3000'); INSERT INTO `salgrade` VALUES ('5', '3001', '9999'); DROP TABLE IF EXISTS `t_job`; CREATE TABLE `t_job` ( `id` int NOT NULL AUTO_INCREMENT, `job` varchar(9) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY (`id`), KEY `j` (`job`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
select查詢的序列號,包含一組數字,表示查詢中執行select子句或者操做表的順序數據庫
id號分爲三種狀況:緩存
一、若是id相同,那麼執行順序從上到下ide
-- 左關聯 explain select * from emp e left join dept d on e.deptno = d.deptno; -- 右關聯 explain select * from emp e right join dept d on e.deptno = d.deptno;
經過left join 和 right join 驗證;id同樣(注意執行計劃的table列),left join 先掃描e表,再掃描d表;right join 先掃描d表,再掃描e表
優化
二、若是id不一樣,若是是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
explain select * from emp e where e.deptno = (select d.deptno from dept d where d.dname = 'SALES');
在下面的表中回先查詢 id 爲 2的數據 也就是咱們的 d表(注意:咱們能夠看到d表中select_type爲 SUBQUERY 也就是子查詢的 意思),而後根據d表中的deptno去查詢 e表中的數據
三、id相同和不一樣的,同時存在:相同的能夠認爲是一組,從上往下順序執行,在全部組中,id值越大,優先級越高,越先執行
explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal where e.deptno = (select d.deptno from dept d where d.dname = 'SALES');
在這裏先從id爲2的執行,若是id是同樣的,就按照順序執行
主要用來分辨查詢的類型,是普通查詢仍是聯合查詢仍是子查詢
select_type 值 | 含義(Meaning) |
---|---|
SIMPLE | 簡單的查詢不包含 UNION 和 subqueries |
PRIMARY | 查詢中若包含任何複雜的子查詢,最外層查詢則被標記爲Primary |
UNION | 若第二個select出如今union以後,則被標記爲union |
DEPENDENT UNION | 跟union相似,此處的depentent表示union或union all聯合而成的結果會受外部表影響 |
UNION RESULT | 從union表獲取結果的select |
SUBQUERY | 在select或者where列表中包含子查詢 |
DEPENDENT SUBQUERY | subquery的子查詢要受到外部表查詢的影響 |
DERIVED | from子句中出現的子查詢,也叫作派生類 |
UNCACHEABLE SUBQUERY | 表示使用子查詢的結果不能被緩存 |
UNCACHEABLE UNION | 表示union的查詢結果不能被緩存 |
--simple:簡單的查詢,不包含子查詢和union explain select * from emp;
--primary:查詢中若包含任何複雜的子查詢,最外層查詢則被標記爲Primary explain select * from emp e where e.deptno = (select d.deptno from dept d where d.dname = 'SALES');
--union:若第二個select出如今union以後,則被標記爲union explain select * from emp where deptno = 10 union select * from emp where sal >2000;
--dependent union:跟union相似,此處的depentent表示union或union all聯合而成的結果會受外部表影響 explain select * from emp e where e.empno in ( select empno from emp where deptno = 10 union select empno from emp where sal >2000);
--union result:從union表獲取結果的select explain select * from emp where deptno = 10 union select * from emp where sal >2000;
--subquery:在select或者where列表中包含子查詢 explain select * from emp where sal > (select avg(sal) from emp) ;
--dependent subquery:subquery的子查詢要受到外部表查詢的影響 explain select * from emp e where e.deptno = (select distinct deptno from dept where deptno = e.deptno);
--DERIVED: from子句中出現的子查詢,也叫作派生類, explain select * from (select ename staname,mgr from emp where ename = 'W' union select ename,mgr from emp where ename = 'E') a;
-- UNCACHEABLE SUBQUERY:表示使用子查詢的結果不能被緩存 explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
--uncacheable union:表示union的查詢結果不能被緩存 explain select * from emp where exists (select 1 from dept where emp.deptno = dept.deptno union select 1 from dept where deptno = 10);
對應行正在訪問哪個表,表名或者別名,多是臨時表或者union合併結果集
一、若是是具體的表名,則代表從實際的物理表中獲取數據,也能夠是表的別名
explain select * from emp where sal > (select avg(sal) from emp) ;
二、表名是derivedN的形式,表示使用了id爲N的查詢產生的衍生表
explain select * from (select ename staname,mgr from emp where ename = 'WARD' union select ename staname,mgr from emp where ename = 'SMITH') a;
derived2 : 代表咱們須要從衍生表2中取數據
三、當有union result的時候,表名是union n1,n2等的形式,n1,n2表示參與union的id
explain select * from emp where deptno = 10 union select * from emp where sal >2000;
type顯示的是訪問類型,訪問類型表示我是以何種方式去訪問咱們的數據,最容易想的是全表掃描,直接暴力的遍歷一張表去尋找須要的數據,效率很是低下,訪問的類型有不少,效率從最好到最壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
通常狀況下,得保證查詢至少達到range級別,最好能達到ref
--all:全表掃描,通常狀況下出現這樣的sql語句並且數據量比較大的話那麼就須要進行優化。 explain select * from emp;
--index:全索引掃描這個比all的效率要好,主要有兩種狀況,一種是當前的查詢時覆蓋索引,即咱們須要的數據在索引中就能夠索取,或者是使用了索引進行排序,這樣就避免數據的重排序 explain select empno from emp;
--range:表示利用索引查詢的時候限制了範圍,在指定範圍內進行查詢,這樣避免了index的全索引掃描,適用的操做符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN() explain select * from emp where empno between 7000 and 7500;
--index_subquery:利用索引來關聯子查詢,再也不掃描全表 explain select * from emp where emp.job in (select job from t_job);
--unique_subquery:該鏈接類型相似與index_subquery,使用的是惟一索引 explain select * from emp e where e.deptno in (select distinct deptno from dept);
--ref_or_null:對於某個字段即須要關聯條件,也須要null值的狀況下,查詢優化器會選擇這種訪問方式 explain select * from emp e where e.mgr is null or e.mgr=7369;
--ref:使用了非惟一性索引進行數據的查找 create index idx_3 on emp(deptno); explain select * from emp e,dept d where e.deptno =d.deptno;
--eq_ref :使用惟一性索引進行數據查找 explain select * from emp,emp2 where emp.empno = emp2.empno;
--const:這個表至多有一個匹配行, explain select * from emp where empno = 7369;
--system:表只有一行記錄(等於系統表),這是const類型的特例,平時不會出現
顯示可能應用在這張表中的索引,一個或多個,查詢涉及到的字段上若存在索引,則該索引將被列出,但不必定被查詢實際使用
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
實際使用的索引,若是爲null,則沒有使用索引,查詢中若使用了覆蓋索引,則該索引和查詢的select字段重疊。
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
表示索引中使用的字節數,能夠經過key_len計算查詢中使用的索引長度,在不損失精度的狀況下長度越短越好。
一、通常地,key_len 等於索引列類型字節長度,例如int類型爲4 bytes,bigint爲8 bytes;
二、若是是字符串類型,還須要同時考慮字符集因素,例如utf8字符集1個字符佔3個字節,gbk字符集1個字符佔2個字節
三、若該列類型定義時容許NULL,其key_len還須要再加 1 bytes
四、若該列類型爲變長類型,例如 VARCHAR(TEXT\BLOB不容許整列建立索引,若是建立部分索引也被視爲動態列類型),其key_len還須要再加 2 bytes
字符集會影響索引長度、數據的存儲空間,爲列選擇合適的字符集;變長字段須要額外的2個字節,固定長度字段不須要額外的字節。而null都須要1個字節的額外空間,因此之前有個說法:索引字段最好不要爲NULL,由於NULL讓統計更加複雜,而且須要額外一個字節的存儲空間。
-- key_len的長度計算公式: -- varchar(len)變長字段且容許NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(變長字段) -- varchar(len)變長字段且不容許NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)+2(變長字段) -- char(len)固定字段且容許NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL) -- char(len)固定字段且不容許NULL : len*(Character Set:utf8=3,gbk=2,latin1=1)
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
顯示索引的哪一列被使用了,若是可能的話,是一個常數
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
根據表的統計信息及索引使用狀況,大體估算出找出所需記錄須要讀取的行數,此參數很重要,直接反應的sql找了多少數據,在完成目的的狀況下越少越好
explain select * from emp;
包含額外的信息。
--using filesort:說明mysql沒法利用索引進行排序,只能利用排序算法進行排序,會消耗額外的位置 explain select * from emp order by sal;
--using temporary:創建臨時表來保存中間結果,查詢完成以後把臨時表刪除 explain select ename,count(*) from emp where deptno = 10 group by ename;
--using index:這個表示當前的查詢時覆蓋索引的,直接從索引中讀取數據,而不用訪問數據表。若是同時出現using where 表名索引被用來執行索引鍵值的查找,若是沒有,表面索引被用來讀取數據,而不是真的查找 explain select deptno,count(*) from emp group by deptno limit 10;
--using where:使用where進行條件過濾 explain select * from emp2 where empno = 1;
到這裏執行計劃就講完了,sql的執行計劃並非很難,主要是記住每一個列表明的意思和如何進行優化,這個是須要大量的訓練和實操實現的, 有興趣的小夥伴能夠自行去試試,仍是頗有趣的,本文只是簡單介紹一下MySQL執行計劃,想全面深刻了解MySQL,可優先閱讀MySQL官方手冊,你們加油~