MySQL優化:explain、show profile和show processlist

前言

要想優化SQL語句,首先得知道SQL語句有什麼問題,哪裏需要被優化。這樣就需要一個SQL語句的監控與量度指標,本文講述的explainshow 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 IOCPU等信息,具體用法如下:

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

總結

學會了explainshow 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
發佈了55 篇原創文章 · 獲贊 107 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章