前言
要想優化SQL語句,首先得知道SQL語句有什麼問題,哪裏需要被優化。這樣就需要一個SQL語句的監控與量度指標,本文講述的explain
和show profile
就是這樣兩個量度SQL語句的命令。
本文主要基於MySQL5.6講解其用法,因爲之後的MySQL版本會去掉show profile
功能。
SQL腳本
本篇使用的表結構以及數據如下
/*Table structure for table `dept` */
CREATE TABLE `dept` (
`deptno` int(2) NOT NULL,
`dname` varchar(15) DEFAULT NULL,
`loc` varchar(15) DEFAULT NULL,
PRIMARY KEY (`deptno`) USING BTREE,
UNIQUE KEY `index_dept_dname` (`dname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
/*Data for the table `dept` */
insert into `dept`(`deptno`,`dname`,`loc`) values
(10,'ACCOUNTING','NewYork'),
(20,'RESEARCH','Dallas'),
(30,'SALES','Chicago'),
(40,'OPERATIONS','Boston');
/*Table structure for table `emp` */
CREATE TABLE `emp` (
`empno` int(4) NOT NULL,
`ename` varchar(10) DEFAULT NULL,
`job` varchar(10) DEFAULT NULL,
`mgr` int(4) DEFAULT NULL,
`hiredate` date DEFAULT NULL,
`sal` decimal(7,0) DEFAULT NULL,
`comm` decimal(7,0) DEFAULT NULL,
`deptno` int(2) DEFAULT NULL,
PRIMARY KEY (`empno`) USING BTREE,
KEY `index_emp_ename` (`ename`),
KEY `index_emp_deptno` (`deptno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
/*Data for the table `emp` */
insert into `emp`(`empno`,`ename`,`job`,`mgr`,`hiredate`,`sal`,`comm`,`deptno`) values
(7369,'SMITH','CLERK',7902,'1980-12-17',800,NULL,20),
(7499,'ALLEN','SALESMAN',7698,'1981-02-20',1600,300,30),
(7521,'WARD','SALESMAN',7698,'1981-02-22',1250,500,30),
(7566,'JONES','MANAGER',7839,'1981-04-02',2975,NULL,20),
(7654,'MARTIN','SALESMAN',7698,'1981-09-28',1250,1400,30),
(7698,'BLAKE','MANAGER',7839,'1981-05-01',2850,NULL,30),
(7782,'CLARK','MANAGER',7839,'1981-06-09',2450,NULL,10),
(7788,'SCOTT','ANALYST',7566,'1987-07-13',3000,NULL,20),
(7839,'KING','PRESIDENT',NULL,'1981-11-17',5000,NULL,10),
(7844,'TURNER','SALESMAN',7698,'1981-09-08',1500,0,30),
(7876,'ADAMS','CLERK',7788,'1987-07-13',1100,NULL,20),
(7900,'JAMES','CLERK',7698,'1981-12-03',950,NULL,30),
(7902,'FORD','ANALYST',7566,'1981-12-03',3000,NULL,20),
(7934,'MILLER','CLERK',7782,'1982-01-23',1300,NULL,10);
使用explain
explain
關鍵字用於獲取SQL語句的執行計劃,描述的是SQL將以何種方式去執行,用法非常簡單,就是直接加在SQL之前。
explain select * from emp
執行結果
mysql> explain select * from emp;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | emp | ALL | NULL | NULL | NULL | NULL | 14 | NULL |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.01 sec)
執行結果中的各個字段代表的含義如下(下文詳解各個字段的意思)
Column | JSON Name | Meaning |
---|---|---|
id | select_id | The SELECT identifier |
select_type | None | The SELECT type |
table | table_name | The table for the output row |
partitions | partitions | The matching partitions |
type | access_type | The join type |
possible_keys | possible_keys | The possible indexes to choose |
key | key | The index actually chosen |
key_len | key_length | The length of the chosen key |
ref | ref | The columns compared to the index |
rows | rows | Estimate of rows to be examined |
filtered | filtered | Percentage of rows filtered by table condition |
Extra | None | Additional information |
id
一系列數字,表示SQL語句執行的序列號,代表了操作的順序。具體原則分爲以下兩點
- id相同時,從上往下執行
- id不同時,數值越大,優先級越高,越先執行
select_type
主要是用來區分查詢的類型,是普通查詢、連接查詢、還是子查詢。值對應的解釋如下
select_type Value | Meaning | 解釋 | 例子 |
---|---|---|---|
SIMPLE | Simple SELECT (not using UNION or subqueries) | 不包含UNION 或子查詢 |
EXPLAIN SELECT * FROM emp e LEFT JOIN dept d ON e.deptno = d.deptno |
PRIMARY | Outermost SELECT | 查詢中包含子查詢,最外層的查詢會被標記成PRIMARY |
EXPLAIN SELECT * FROM emp e WHERE e.deptno = (SELECT d.deptno FROM dept d WHERE d.dname = 'SALES') |
UNION | Second or later SELECT statement in a UNION | 出現在UNION 之後的語句會被標記成UNION |
EXPLAIN SELECT * FROM emp WHERE empno = 7369 UNION SELECT * FROM emp WHERE empno = 7499 UNION SELECT * FROM emp WHERE empno = 7521 |
DEPENDENT UNION | Second or later SELECT statement in a UNION, dependent on outer query | 和UNION 類似,但是結果取決於外部查詢 |
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 | Result of a UNION. | UNION 的結果 |
同·UNION · |
SUBQUERY | First SELECT in subquery | 子查詢中的第一個SELECT 子句 |
同PRIMARY |
DEPENDENT SUBQUERY | First SELECT in subquery, dependent on outer query | 和SUBQUERY 類似,但是結果取決於外部查詢 |
同DEPENDENT UNION |
DERIVED | Derived table | FROM 語句中出現的子查詢,也叫派生表 |
EXPLAIN SELECT * FROM (SELECT e.empno, e.ename, e.deptno FROM emp e) t |
UNCACHEABLE SUBQUERY | A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query | / | / |
UNCACHEABLE UNION | The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY) | / | / |
table
表示正在訪問哪一張表,是表名或者別名。也有可能是臨時表或者union
的結果集。
<unionM,N>
:是指ID值爲M和N的行的並集。<derivedN>
:引用ID值爲N的行的派生表結果。派生表可能來自例如FROM子句中的子查詢。<subqueryN>
:引用ID值爲N的行的實例化子查詢的結果
type
描述如何聯接表,表示SQL語句以何種方式去訪問表,找到對應的數據。訪問類型有很多,效率從高到低,分別爲
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般情況下,得保證查詢至少達到range級別,最好能達到ref
type | 描述 | 例子 |
---|---|---|
ALL | 全表掃描,通常利用索引來避免 | explain select * from emp |
index | 全索引掃描,效率比ALL 高,通常包括兩種情況當前查詢用到了索引覆蓋,所需的數據可以在索引中直接獲取 當前查詢利用了索引進行排序,這樣就可以避免數據的重新排序 |
EXPLAIN SELECT e.ename FROM emp e |
range | 使用索引的時候限制了範圍,避免了index 類型的全索引掃描實用範圍 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, IN() |
EXPLAIN SELECT * FROM emp e WHERE e.ename LIKE 'SMITH%' |
index_subquery | 利用索引來關聯子查詢 | / |
unique_subquery | 類似index_subquery ,但是使用的是唯一索引 |
/ |
index_merge | 需要多個索引組合使用 | / |
ref_or_null | 對某個查詢條件既需要關聯條件,又需要null | EXPLAIN SELECT * FROM emp e WHERE e.deptno = 20 OR e.deptno IS NULL |
fulltext | 使用FULLTEXT 索引執行連接 |
/ |
ref | 使用非唯一的索引進行查找,和ref_or_null 類似,但是不需要null |
EXPLAIN SELECT * FROM emp e WHERE e.deptno = 20 |
eq_ref | 使用PRIMARY KEY 或者UNIQUE NOT NULL 索引進行連接查詢 |
EXPLAIN SELECT * FROM emp e1 LEFT JOIN emp e2 ON e1.empno = e2.empno |
const | 查詢最多能匹配一條記錄 | EXPLAIN SELECT * FROM emp WHERE empno = 7369 |
system | 表只有一行記錄,const 的特例 |
/ |
possible_keys
顯示一個或者多個可能用於該SQL的索引。
EXPLAIN SELECT * FROM emp e WHERE e.`ename` LIKE 'SIM%' AND e.`deptno` = 10
執行結果
mysql> EXPLAIN SELECT * FROM emp e WHERE e.`ename` LIKE 'SIM%' AND e.`deptno` = 10;
+----+-------------+-------+-------+----------------------------------+-----------------+---------+------+------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------------------------+-----------------+---------+------+------+------------------------------------+
| 1 | SIMPLE | e | range | index_emp_ename,index_emp_deptno | index_emp_ename | 33 | NULL | 1 | Using index condition; Using where |
+----+-------------+-------+-------+----------------------------------+-----------------+---------+------+------+------------------------------------+
1 row in set (0.00 sec)
key
實際使用到的索引,如果NULL
表示沒有使用索引
EXPLAIN SELECT * FROM emp e WHERE e.`ename` LIKE 'SIM%' AND e.`deptno` = 10
執行結果
mysql> EXPLAIN SELECT * FROM emp e WHERE e.`ename` LIKE 'SIM%' AND e.`deptno` = 10;
+----+-------------+-------+-------+----------------------------------+-----------------+---------+------+------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------------------------+-----------------+---------+------+------+------------------------------------+
| 1 | SIMPLE | e | range | index_emp_ename,index_emp_deptno | index_emp_ename | 33 | NULL | 1 | Using index condition; Using where |
+----+-------------+-------+-------+----------------------------------+-----------------+---------+------+------+------------------------------------+
1 row in set (0.00 sec)
key_len
表示索引中使用的字節數,在滿足需求的情況下,值越小越好。
ref
表示將哪些列或常量與索引進行比較,以從表中選擇記錄。
-
列名
EXPLAIN SELECT * FROM emp e1 LEFT JOIN emp e2 ON e1.`empno` = e2.`empno`
執行結果
mysql> EXPLAIN SELECT * FROM emp e1 LEFT JOIN emp e2 ON e1.`empno` = e2.`empno`; +----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------+ | 1 | SIMPLE | e1 | ALL | NULL | NULL | NULL | NULL | 14 | NULL | | 1 | SIMPLE | e2 | eq_ref | PRIMARY | PRIMARY | 4 | test.e1.empno | 1 | NULL | +----+-------------+-------+--------+---------------+---------+---------+---------------+------+-------+ 2 rows in set (0.00 sec)
ref
列中test.e1.empno
分別表示數據庫名、表(別)名,字段名。 -
常量
EXPLAIN SELECT * FROM emp WHERE ename = 'CLERK'
執行結果
mysql> EXPLAIN SELECT * FROM emp WHERE ename = 'CLERK'; +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+ | 1 | SIMPLE | emp | ref | index_emp_ename | index_emp_ename | 33 | const | 1 | Using index condition | +----+-------------+-------+------+-----------------+-----------------+---------+-------+------+-----------------------+ 1 row in set (0.00 sec)
如果該值爲func
,則使用的值是某些函數的結果。
rows
該SQL語句需要訪問的大致行數,是一個估計值。但是這個值非常重要,在滿足需求的情況下,越小越好。
extra
額外信息。其中常見比較重要的幾種如下
解釋 | 例子 | |
---|---|---|
using filesort | 無法僅利用索引就完成排序,只能利用排序算法進行排序,會消耗額外的空間 | EXPLAIN SELECT * FROM emp ORDER BY hiredate ASC |
using temporary | 建立臨時表來保存中間結果,查詢完成之後把臨時表刪除 | EXPLAIN SELECT ename ,COUNT(*) FROM emp WHERE deptno = 10 GROUP BY ename |
using index | 表示當前查詢滿足索引覆蓋 | EXPLAIN SELECT ename FROM emp |
using where | 使用where 條件進行過濾 |
EXPLAIN SELECT ename FROM emp WHERE ename = 'SMITH' |
impossible where | where 條件的結果總是false |
EXPLAIN SELECT * FROM emp WHERE 1 = 2 |
以上便是explain
關鍵字的使用方式以及含義,這個關鍵字的作用主要用來分析索引使用情況。
需要了解的是:使用explain
關鍵字進行分析時,SQL語句並不會執行。只是模擬MySQL優化器的執行過程,所以用explain
查看的結果是叫執行計劃。
使用show profile
explain
關鍵字主要用來定性分析索引的使用情況,以及SQL語句的優劣,但是無法知道SQL語句的實際執行情況。
而show profile
命令可以做到定量分析SQL語句的執行情況。即使用者可以明確知道一條SQL到底執行了多久。
想要使用這個命令,主要步驟可以分爲四步:
-
首先設置屬性,
set profileing=1;
開啓了這個屬性後,再執行SQL語句,就會記錄SQL語句執行各個步驟耗時
-
接着執行多條SQL語句
select * from emp; select * from dept;
執行結果不重要,主要關注各個SQL語句的執行時間
-
接下來再執行如下語句,顯示統計成功的SQL語句
show profiles;
執行結果
mysql> show profiles; +----------+------------+--------------------+ | Query_ID | Duration | Query | +----------+------------+--------------------+ | 1 | 0.00065025 | select * from emp | | 2 | 0.00626150 | select * from dept | +----------+------------+--------------------+ 2 rows in set, 1 warning (0.00 sec)
可以看到MySQL已經統計了上面執行的兩條SQL語句
-
如果想具體查看SQL語句各個步驟的詳細耗時,接着執行如下SQL語句
## 查看第二條SQL語句執行耗時的詳細信息 show profile for query 2
執行結果
+----------------------+----------+ | Status | Duration | +----------------------+----------+ | starting | 0.000164 | | checking permissions | 0.000054 | | Opening tables | 0.004434 | | init | 0.000037 | | System lock | 0.000013 | | optimizing | 0.000007 | | statistics | 0.000013 | | preparing | 0.000014 | | executing | 0.000004 | | Sending data | 0.001350 | | end | 0.000013 | | query end | 0.000007 | | closing tables | 0.000012 | | freeing items | 0.000123 | | cleaning up | 0.000018 | +----------------------+----------+ 15 rows in set, 1 warning (0.03 sec)
執行結果展示個各個步驟以及持續的時間。
show profile語法
show profile
完整的語法如下:
SHOW PROFILE [type [, type] ... ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type: {
ALL
| BLOCK IO
| CONTEXT SWITCHES
| CPU
| IPC
| MEMORY
| PAGE FAULTS
| SOURCE
| SWAPS
}
各個type對應的信息如下
type | 解釋 |
---|---|
ALL | 顯示所有性能信息 |
BLOCK IO | 顯示塊io的次數 |
CONTEXT SWITCHES | 顯示上下文切換的次數,包括主動和被動 |
CPU | 顯示系統和用戶CPU使用時間 |
IPC | 顯示發送和接收消息的次數 |
MEMORY | 暫未實現 |
PAGE FAULTS | 顯示頁面錯誤的數量 |
SOURCE | 顯示源代碼中的函數名稱以及該函數所在文件的名稱和行號 |
SWAPS | 顯示swap的次數 |
也就是說除了各個步驟持續的時間,還可以看到BLOCK IO
、CPU
等信息,具體用法如下:
show profile block io, cpu for query 2
執行結果:
+----------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting | 0.000164 | 0.000000 | 0.000000 | NULL | NULL |
| checking permissions | 0.000054 | 0.000000 | 0.000000 | NULL | NULL |
| Opening tables | 0.004434 | 0.000000 | 0.000000 | NULL | NULL |
| init | 0.000037 | 0.000000 | 0.000000 | NULL | NULL |
| System lock | 0.000013 | 0.000000 | 0.000000 | NULL | NULL |
| optimizing | 0.000007 | 0.000000 | 0.000000 | NULL | NULL |
| statistics | 0.000013 | 0.000000 | 0.000000 | NULL | NULL |
| preparing | 0.000014 | 0.000000 | 0.000000 | NULL | NULL |
| executing | 0.000004 | 0.000000 | 0.000000 | NULL | NULL |
| Sending data | 0.001350 | 0.000000 | 0.000000 | NULL | NULL |
| end | 0.000013 | 0.000000 | 0.000000 | NULL | NULL |
| query end | 0.000007 | 0.000000 | 0.000000 | NULL | NULL |
| closing tables | 0.000012 | 0.000000 | 0.000000 | NULL | NULL |
| freeing items | 0.000123 | 0.000000 | 0.000000 | NULL | NULL |
| cleaning up | 0.000018 | 0.000000 | 0.000000 | NULL | NULL |
+----------------------+----------+----------+------------+--------------+---------------+
15 rows in set, 1 warning (0.00 sec)
補充
需要注意的是,show profile
方式將從5.6.7開始不推薦使用,並且在以後的版本中會刪除,改用Performance Schema
使用show processlist
show processlist
命令可以查看當前MySQL實例的連接情況,用於觀察是否有大量的連接處於非正常狀態。用法非常簡單,直接使用就行
show processlist
執行結果
mysql> show processlist;
+----+------+----------------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+----------------+------+---------+------+-------+------------------+
| 7 | root | localhost:2353 | test | Sleep | 57 | | NULL |
| 8 | root | localhost:3811 | NULL | Query | 0 | init | show processlist |
+----+------+----------------+------+---------+------+-------+------------------+
2 rows in set (0.00 sec)
可以看到我的MySQL實例當前有2個連接。其中各個字段的含義如下
字段 | 解釋 |
---|---|
Id | 連接標識符 |
User | 當前用戶 |
Host | 操作的主機,指客戶端 |
db | 默認數據庫(如果已選擇);否則爲NULL |
Command | 線程正在執行的命令類型 |
Time | 線程處於其當前狀態的持續時間(以秒爲單位) |
State | 指示線程正在執行的操作,事件或狀態 |
Info | 線程正在執行的語句,如果未執行任何語句,則爲NULL。 該語句可能是發送到服務器的那條語句,或者是最內部的語句(如果該語句執行其他語句,比如存儲過程中的select語句) |
對於Command
字段,對應的狀態如下:
- sleep:正在等待客戶端發送新的請求
- query:正在執行查詢或者正在將結果發送給客戶端
- locked:在MySQL服務層,線程正在等待表鎖
- analyzing and statistics:線程正在收集存儲引擎的統計信息,並生成查詢的執行計劃
- copying to tmp table:線程正在執行查詢,並且將其結果集都複製到一個臨時表中
- sorting result:正在對結果集進行排序
- sending data:線程可能在多個狀態之間傳送數據,或者在生成結果集或者向客戶端返回數據
show processlist
命令默認Info
字段最多顯示每條語句的前100個字符,如果想完全顯示,可以使用show full processlist
總結
學會了explain
和show profile
這兩個命令,足以應用於一些比較簡單的性能分析場景。分析出SQL語句存在的問題,從而寫出更優質的SQL語句。
show processlist
命令則是用來管理MySQL實例的連接情況,如果收到類似too many connections
的錯誤,使用此命令將非常有用。
參考
- https://dev.mysql.com/doc/refman/5.6/en/explain-output.html
- https://dev.mysql.com/doc/refman/5.6/en/show-profile.html
- https://dev.mysql.com/doc/refman/5.6/en/show-processlist.html