如何分析SQL執行效率(上)

前言

SQL優化對於一枚程序員來說是至關重要的,並且大部分面試中,都會問道有關sql優化的一 些問題,這裏將帶着大家學會如何分析sql執行效率,首先要想優化一條sql語句,前提是我們要能夠定位到查詢慢的sql語句,並對其進行分析,找到慢查詢的圓心,然後進行優化。

定位慢 SQL

大家在工作中測試的時候偶爾會碰到查詢結果,超過一定時間才返回,這時我們就應該考慮是不是慢查詢導致的,接下來讓我們看看如果定位慢sql語句

一 通過慢查詢日誌

如果需要定位到慢查詢,一般的方法是通過慢查詢日誌來查詢的,MySQL 的慢查詢日誌它會記錄在 MySQL 中響應時間超過參數 long_query_time(單位秒,默認值 10)設置的值並且掃描記錄數不小於min_examined_row_limit(默認值位0)的語句,所以它會幫我們找出查詢慢的sql語句。

默認情況下,慢查詢日誌中並不會記錄管理語句,可通過設置參數 log_slow_admin_statements = on 讓管理語句中的慢查詢也會記錄到慢查詢的日誌中。

默認情況下,也不會記錄查詢時間不超過 long_query_time 但是不使用索引的語句,可通過配置 log_queries_not_using_indexes = on 讓不使用索引的 SQL 都被記錄到慢查詢日誌中(即使查詢時間沒超過 long_query_time 配置的值)。

通常我們開啓慢查詢日誌一般分爲4給步驟:
第一步 開啓慢查詢日誌
首先開啓慢查詢日誌,由參數 slow_query_log 決定是否開啓,在 MySQL 的命令行下輸入下面的命令:

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 中 long_query_time 的值如何確定呢?

線上業務一般建議把 long_query_time 設置爲 1 秒,如果某個業務的 MySQL 要求比較高的 QPS,可設置慢查詢爲 0.1
秒。發現慢查詢及時優化或者提醒開發改寫。

一般測試環境建議 long_query_time 設置的閥值比生產環境的小,比如生產環境是 1 秒,則測試環境建議配置成 0.5
秒。便於在測試環境及時發現一些效率低的 SQL。

甚至某些重要業務測試環境 long_query_time 可以設置爲
0,以便記錄所有語句。並留意慢查詢日誌的輸出,上線前的功能測試完成後,分析慢查詢日誌每類語句的輸出,重點關注
Rows_examined(語句執行期間從存儲引擎讀取的行數),提前優化。

第三步 確定慢查詢日誌文件路徑
慢查詢日誌的路徑默認是 MySQL 的數據目錄

mysql> show global variables like "datadir";

+---------------+------------------------+
| Variable_name | Value                  |
+---------------+------------------------+
| datadir       | /data/mysql/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 | mysql-slow.log |
+---------------------+----------------+

1 row in set (0.00 sec)

根據上面的查詢結果,可以直接查看 /data/mysql/data/3306/mysql-slow.log 文件獲取已經執行完的慢查詢

[root@mysqltest ~]# tail -n5 /data/mysql/data/3306/mysql-slow.log

Time: 2019-05-21T09:15:06.255554+08:00

User@Host: root[root] @ localhost []  Id: 8591152

Query_time: 10.000260  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0

SET timestamp=1558401306;
select sleep(10);

這裏對上方的執行結果詳細描述一下:

tail -n5:只查看慢查詢文件的最後 5
Time:慢查詢發生的時間
User@Host:客戶端用戶和 IP
Query_time:查詢時間
Lock_time:等待表鎖的時間
Rows_sent:語句返回的行數
Rows_examined:語句執行期間從存儲引擎讀取的行數

小夥伴們可以使用 pt-query-digest 或者 mysqldumpslow 等工具對慢查詢日誌進行分析

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

如果不使用 FULL 關鍵字,在 info 字段中只顯示每個語句的前 100 個字符,如果想看語句的全部內容可以使用 full 修飾(show full processlist)。

mysql> show processlist\G`

`......`

`*************************** 10. row ***************************`

     `Id: 7651833`

   `User: one`

   `Host: 192.168.1.251:52154`

     `db: ops`

`Command: Query`

   `Time: 3`

  `State: User sleep`

   `Info: select sleep(10)`

`......`

`10 rows in set (0.00 sec)`

這裏對上面結果解釋一下:

Time:表示執行時間
Info:表示 SQL 語句
我們這裏可以通過它的執行時間(Time)來判斷是否是慢 SQL。

上面講了如果定位到慢查詢,接下來,就讓我們看看怎麼分析定位到的慢查詢了

使用 explain 分析慢查詢

我們可以通過 explain、show profile 和 trace 等診斷工具來分析慢查詢。

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

爲了便於理解,先創建兩張測試表,建表及數據寫入語句如下:

CREATE DATABASE muke;           /* 創建測試使用的database,名爲muke */
use muke;                       /* 使用muke這個database */
drop table if exists t1;        /* 如果表t1存在則刪除表t1 */

CREATE TABLE `t1` (             /* 創建表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;	

drop procedure if exists insert_t1; /* 如果存在存儲過程insert_t1,則刪除 */
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 ;                 /* 創建批量寫入1000條數據到表t1的存儲過程insert_t1 */
call insert_t1();           /* 運行存儲過程insert_t1 */

drop table if exists t2;    /* 如果表t2存在則刪除表t2 */
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 附加信息
其中 explain 各列都有各種不同的值,這裏介紹幾個比較重要列常包含的值:包含 select_typ、type 和 Extra。

select_type

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

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

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;

總結

本文主要講到如何定位慢 SQL:

一種方法是查看慢查詢日誌
另一種方法是 show process 查看正在執行的 SQL
再講到通過 explain 分析慢 SQL,explain 會返回很多字段,其中 select_type、type、key、rows、Extra 是重點關注項。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章