MYSQL 性能分析方法
1、性能優化概述
MySQL Query Optimizer 的作用
- MySQL 中有專門負責優化SELECT語句的優化器模塊,主要功能:通過計算分析系統中收集到的統計信息,為客戶端請求的Query提供他認為最優的執行計划(MySQL認為最優的數據檢索方式,但不見得是DBA認為是最優的,這部分最耗費時間)
- 當客戶端向MySQL 請求一條Query,命令解析器模塊完成請求分類,區別出是SELECT並轉發給MySQL Query Optimizer時,MySQL Query Optimizer 首先會對整條Query進行優化,處理掉一些常量表達式的預算,直接換算成常量值。並對Query中的查詢條件進行簡化和轉換,如去掉一些無用或顯而易見的條件、結構調整等。然后分析 Query中的Hint信息(如果有),看顯示Hint信息是否可以完全確定該Query的執行計划。如果沒有Hint 或Hint 信息還不足以完全確定執行計划,則會讀取所涉及對象的統計信息,根據Query進行寫相應的計算分析,然后再得出最后的執行計划。
MySQL 常見瓶頸
- CPU 瓶頸:CPU在飽和的時候一般發生在數據裝入在內存或從磁盤上讀取數據時候
- IO 瓶頸:磁盤I/O瓶頸發生在裝入數據遠大於內存容量時
- 服務器硬件的性能瓶頸:top、free、iostat和vmstat來查看系統的性能狀態
2、Explain 概述
Explain
是什么#?Explain 是查看執行計划
- 使用EXPLAIN關鍵字可以模擬優化器執行SQL語句,從而知道MySQL是如何處理你的SQL語句的。分析你的查詢語句或是結構的性能瓶頸
- 官網地址:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
能干嘛?
- 表的讀取順序(id 字段)
- 數據讀取操作的操作類型(select_type 字段)
- 哪些索引可以使用(possible_keys 字段)
- 哪些索引被實際使用(keys 字段)
- 表之間的引用(ref 字段)
- 每張表有多少行被優化器查詢(rows 字段)
怎么玩?
- Explain + SQL語句
mysql> explain select * from tbl_emp;
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | tbl_emp | ALL | NULL | NULL | NULL | NULL | 8 | NULL |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)
3、Explain 詳解
id☆
id:select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序
id 取值的三種情況:
-
id相同,執行順序由上至下
-
id不同,如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
-
id相同不同,同時存在:id如果相同,可以認為是一組,從上往下順序執行;在所有組中,id值越大,優先級越高,越先執行;衍生=DERIVED
select_type
select_type:查詢的類型,主要用於區別普通查詢、聯合查詢、子查詢等復雜查詢
- SIMPLE:簡單的select查詢,查詢中不包含子查詢或者UNION
- PRIMARY:查詢中若包含任何復雜的子部分,最外層查詢則被標記為PRIMARY
- SUBQUERY:在SELECT或者WHERE列表中包含了子查詢
- DERIVED:在FROM列表中包含的子查詢被標記為DERIVED(衍生)MySQL會遞歸執行這些子查詢,把結果放在臨時表里
- UNION:若第二個SELECT出現在UNION之后,則被標記為UNION;若UNION包含在FROM子句的子查詢中,外層SELECT將被標記為:DERIVED
- UNION RESULT:從UNION表獲取結果的SELECT
UNION 和 UNION RESULT舉例
xplain
-> select * from tbl_emp e left join tbl_dept d on e.deptId = d.id
-> union
-> select * from tbl_emp e right join tbl_dept d on e.deptId = d.id;
+----+--------------+------------+------+---------------+------------+---------+-----------+------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+------------+------+---------------+------------+---------+-----------+------+----------------------------------------------------+
| 1 | PRIMARY | e | ALL | NULL | NULL | NULL | NULL | 8 | NULL |
| 1 | PRIMARY | d | ALL | PRIMARY | NULL | NULL | NULL | 5 | Using where; Using join buffer (Block Nested Loop) |
| 2 | UNION | d | ALL | NULL | NULL | NULL | NULL | 5 | NULL |
| 2 | UNION | e | ref | fk_dept_Id | fk_dept_Id | 5 | db01.d.id | 1 | NULL |
| NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------+---------------+------------+---------+-----------+------+----------------------------------------------------+
5 rows in set (0.00 sec)
table
table:顯示這一行的數據是關於哪張表的
type☆
type:訪問類型排列,顯示查詢使用了何種類型
- type顯示的是訪問類型,是較為重要的一個指標,結果值從最好到最壞依次是:
system>const>eq_ref>ref>fultext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
- 挑重要的來說:
system>const>eq_ref>ref>range>index>ALL
,一般來說,得保證查詢至少達到range級別,最好能達到ref。
從最好到最差依次是:system>const>eq_ref>ref>range>index>ALL
-
system:表只有一行記錄(等於系統表),這是const類型的特例,平時不會出現,這個也可以忽略不計
-
const:表示通過索引一次就找到了,const用於比較primary key或者unique索引。因為只匹配一行數據,所以很快。如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量
-
eq_ref:唯一性索引,對於每個索引鍵,表中只有一條記錄與之匹配,常見於主鍵或唯一索引掃描
-
ref:非唯一索引掃描,返回匹配某個單獨值的所有行。本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,所以他應該屬於查找和掃描的混合體
-
range:只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引一般就是在你的where語句中出現了
between
、<
、>
、in
等的查詢這種范圍掃描索引掃描比全表掃描要好,因為他只需要開始索引的某一點,而結束於另一點,不用掃描全部索引 -
index:Full Index Scan,index與ALL區別為index類型只遍歷索引樹。這通常比ALL快,因為索引文件通常比數據文件小。(也就是說雖然all和index都是讀全表,但index是從索引中讀取的,而all是從硬盤數據庫文件中讀的)
-
all:FullTable Scan,將遍歷全表以找到匹配的行(全表掃描)
-
備注:一般來說,得保證查詢只是達到range級別,最好達到ref
possible_keys
possible_keys
- 顯示可能應用在這張表中的索引,一個或多個
- 若查詢涉及的字段上存在索引,則該索引將被列出,但不一定被查詢實際使用
key
key
-
實際使用的索引,如果為null,則沒有使用索引
-
若查詢中使用了覆蓋索引,則該索引僅出現在key列表中
key_len
key_len
- 表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好
- key_len顯示的值為索引最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的

ref
ref
- 顯示索引哪一列被使用了,如果可能的話,最好是一個常數。哪些列或常量被用於查找索引列上的值
- 由key_len可知t1表的索引idx_col1_col2被充分使用,t1表的col1匹配t2表的col1,t1表的col2匹配了一個常量,即’ac’

rows
rows
根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數

Extra☆
Extra:包含不適合在其他列中顯示但十分重要的額外信息
-
Using filesort(文件排序):
- MySQL中無法利用索引完成排序操作成為“文件排序”
- 說明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取
- 出現 Using filesort 不好(九死一生),需要盡快優化 SQL
- 示例中第一個查詢只使用了 col1 和 col3,原有索引派不上用場,所以進行了外部文件排序
- 示例中第二個查詢使用了 col1、col2 和 col3,原有索引派上用場,無需進行文件排序
-
Using temporary(創建臨時表):
- 使用了臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by
- 出現 Using temporary 超級不好(十死無生),需要立即優化 SQL
- 示例中第一個查詢只使用了 col1,原有索引派不上用場,所以創建了臨時表進行分組
- 示例中第二個查詢使用了 col1、col2,原有索引派上用場,無需創建臨時表
-
Using index(覆蓋索引):
-
表示相應的select操作中使用了覆蓋索引(Coveing Index),避免訪問了表的數據行,效率不錯!
-
如果同時出現using where,表明索引被用來執行索引鍵值的查找
-
如果沒有同時出現using where,表明索引用來讀取數據而非執行查找動作
-
覆蓋索引(Covering Index),也說為索引覆蓋
- 理解方式一:就是**select的數據列只用從索引中就能夠取得,不必讀取數據行**,MySQL可以**利用索引返回select列表中的字段,而不必根據索引再次讀取數據文件**,換句話說查詢列要被所建的索引覆蓋。 - 理解方式二:索引是高效找到行的一個方法,但是一般數據庫也能使用索引找到一個列的數據,因此它不必讀取整個行。畢竟索引葉子節點存儲了它們索引的數據;當能通過讀取索引就可以得到想要的數據,那就不需要讀取行了。一個索引包含了(或覆蓋了)滿足查詢結果的數據就叫做覆蓋索引。 - 注意:**如果要使用覆蓋索引,一定要注意select列表中只取出需要的列,不可`select *`** ,因為如果將所有字段一起做索引會導致索引文件過大,查詢性能下降。
-
-
Using where:表明使用了where過濾
-
Using join buffer:表明使用了連接緩存
-
impossible where:where子句的值總是false,不能用來獲取任何元組
-
select tables optimized away:在沒有GROUPBY子句的情況下,基於索引優化MIN/MAX操作或者對於MyISAM存儲引擎優化
COUNT(*)
操作,不必等到執行階段再進行計算,查詢執行計划生成的階段即完成優化。 -
distinct:優化distinct,在找到第一匹配的元組后即停止找同樣值的工作