1. 程式人生 > 實用技巧 >分析SQL執行效率(一)

分析SQL執行效率(一)

定位慢 SQL

定位慢 SQL 的兩種方案

  • 檢視慢查詢日誌確定已經執行完的慢查詢

  • show processlist 檢視正在執行的慢查詢

通過慢查詢日誌

MySQL 的慢查詢日誌用來記錄在 MySQL 中響應時間超過引數 long_query_time(單位秒,預設值 10)設定的值並且掃描記錄數不小於 min_examined_row_limit(預設值 0)的語句

使用慢查詢日誌,一般分為四步:開啟慢查詢日誌、設定慢查詢閥值、確定慢查詢日誌路徑、確定慢查詢日誌的檔名。

首先開啟慢查詢日誌,由引數 slow_query_log 決定是否開啟,預設環境下,慢查詢日誌是關閉的

mysql> set global slow_query_log = on;
Query OK, 0 rows affected (0.00 sec)

設定慢查詢時間閥值

mysql> set global long_query_time = 1;
Query OK, 0 rows affected (0.00 sec)

慢查詢日誌的路徑預設是 MySQL 的資料目錄

mysql> show global variables like "datadir";
+---------------+-------------+
| Variable_name | Value       |
+---------------+-------------+
| datadir       | /data/3306/ |
+---------------+-------------+
1 row in set (0.00 sec)

確定慢查詢日誌的檔名

mysql> show global variables like "slow_query_log_file";
+---------------------+------------------------------+
| Variable_name       | Value                        |
+---------------------+------------------------------+
| slow_query_log_file | /data/3306/mysql_01-slow.log |
+---------------------+------------------------------+
1 row in set (0.01 sec)

根據上面的查詢結果,可以直接檢視 /data/3306/mysql_01-slow.log 檔案獲取已經執行完的慢查詢

[root@mysql_01 ~]# tail -n5 /data/3306/mysql_01-slow.log 
# Time: 2020-06-17T03:47:28.224746Z
# User@Host: root[root] @ localhost []  Id:     5
# Query_time: 10.003112  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET timestamp=1592365648;
select sleep(10);
[root@mysql_01 ~]# 

tail -n5 :只檢視慢查詢檔案的最後 5 行

Time:慢查詢發生的時間

User@Host:客戶端使用者和 IP

Query_time:查詢時間

Lock_time:等待表鎖的時間

Rows_sent:語句返回的行數

Rows_examined:語句執行期間從儲存引擎讀取的行數

通過 show processlist

有時慢查詢正在執行,已經導致資料庫負載偏高了,而由於慢查詢還沒執行完,因此慢查詢日誌還看不到任何語句。此時可以使用 show processlist 命令判斷正在執行的慢查詢。show processlist 顯示哪些執行緒正在執行。如果有 PROCESS 許可權,則可以看到所有執行緒。否則,只能看到當前會話的執行緒。

mysql> show processlist\G
*************************** 1. row ***************************
     Id: 8
   User: root
   Host: localhost
     db: NULL
Command: Query
   Time: 0
  State: starting
   Info: show processlist
1 row in set (0.00 sec)

Time:表示執行時間

Info:表示 SQL 語句

可以通過它的執行時間(Time)來判斷是否是慢 SQL

使用 explain 分析慢查詢

explain 可以獲取 MySQL 中 SQL 語句的執行計劃,比如語句是否使用了關聯查詢、是否使用了索引、掃描行數等。可以幫我們選擇更好地索引和寫出更優的 SQL 。使用方法:在查詢語句前面加上 explain 執行就可以了

為了便於理解,先建立兩張測試表

create database muke;
use muke;

CREATE TABLE `t1` (
	`id` int(11) NOT NULL auto_increment,
	`a` int(11) DEFAULT NULL,
	`b` int(11) DEFAULT NULL,
	`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '記錄建立時間',
	`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '記錄更新時間',
	PRIMARY KEY (`id`),
	KEY `idx_a` (`a`),
	KEY `idx_b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

delimiter $
create procedure insert_t1()	/* 建立儲存過程insert_t1 */
begin
    declare i int;				/* 宣告變數i */
    set i=1;				/* 設定i的初始值為1 */
    while(i<=1000) do			/* 對滿足i<=1000的值進行while迴圈 */
	insert into t1(a, b) values(i, i); 		/* 寫入表t1中a、b兩個欄位,值都為i當前的值 */
	set i=i+1;				/* 將i加1 */
    end while;
end $
delimiter ;
call insert_t1();				/* 執行儲存過程insert_t1 */

create table t2 like t1;			/* 建立表t2,表結構與t1一致 */
insert into t2 select * from t1;		/* 將表t1的資料匯入到t2 */

下面嘗試使用 explain 分析一條 SQL,例子如下:

mysql> explain select * from t1 where b=100;

列名 解釋
id 查詢編號
select_type 查詢型別:顯示本行是簡單還是複雜查詢
table 涉及到的表
partitions 匹配的分割槽:查詢將匹配記錄所在的分割槽。僅當使用 partition 關鍵字時才顯示該列。對於非分割槽表,該值為 NULL。
type 本次查詢的表連線型別
possible_keys 可能選擇的索引
key 實際選擇的索引
key_len 被選擇的索引長度:一般用於判斷聯合索引有多少列被選擇了
ref 與索引比較的列
rows 預計需要掃描的行數,對 InnoDB 來說,這個值是估值,並不一定準確
filtered 按條件篩選的行的百分比
Extra 附加資訊

select_type

select_type 的值 解釋
SIMPLE 簡單查詢 (不使用關聯查詢或子查詢)
PRIMARY 如果包含關聯查詢或者子查詢,則最外層的查詢部分標記為 primary
UNION 聯合查詢中第二個及後面的查詢
DEPENDENT UNION 滿足依賴外部的關聯查詢中第二個及以後的查詢
UNION RESULT 聯合查詢的結果
SUBQUERY 子查詢中的第一個查詢
DEPENDENT SUBQUERY 子查詢中的第一個查詢,並且依賴外部查詢
DERIVED 用到派生表的查詢
MATERIALIZED 被物化的子查詢
UNCACHEABLE SUBQUERY 一個子查詢的結果不能被快取,必須重新評估外層查詢的每一行
UNCACHEABLE UNION 關聯查詢第二個或後面的語句屬於不可快取的子查詢

type

type 的值 解釋
system 查詢物件表只有一行資料,且只能用於 MyISAM 和 Memory 引擎的表,這是最好的情況
const 基於主鍵或唯一索引查詢,最多返回一條結果
eq_ref 表連線時基於主鍵或非 NULL 的唯一索引完成掃描
ref 基於普通索引的等值查詢,或者表間等值連線
fulltext 全文檢索
ref_or_null 表連線型別是 ref,但進行掃描的索引列中可能包含 NULL 值
index_merge 利用多個索引
unique_subquery 子查詢中使用唯一索引
index_subquery 子查詢中使用普通索引
range 利用索引進行範圍查詢
index 全索引掃描
ALL 全表掃描

上表的這些情況,查詢效能從上到下依次是最好到最差。

extra

Extra 常見的值 解釋 例子
Using filesort 將用外部排序而不是索引排序,資料較小時從記憶體排序,否則需要在磁碟完成排序 explain select * from t1 order by create_time;
Using temporary 需要建立一個臨時表來儲存結構,通常發生對沒有索引的列進行 GROUP BY 時 explain select * from t1 group by create_time;
Using index 使用覆蓋索引 explain select a from t1 where a=111;
Using where 使用 where 語句來處理結果 explain select * from t1 where create_time=‘2019-06-18 14:38:24’;
Impossible WHERE 對 where 子句判斷的結果總是 false 而不能選擇任何資料 explain select * from t1 where 1<0;
Using join buffer (Block Nested Loop) 關聯查詢中,被驅動表的關聯欄位沒索引 explain select * from t1 straight_join t2 on (t1.create_time=t2.create_time);
Using index condition 先條件過濾索引,再查資料 explain select * from t1 where a >900 and a like “%9”;
Select tables optimized away 使用某些聚合函式(比如 max、min)來訪問存在索引的某個欄位是 explain select max(a) from t1;