天天看點

MySQL之Explain性能分析

作者:程式員阿龍

一、Explain 概述

使用 EXPLAIN 關鍵字可以模拟優化器來執行SQL查詢語句,進而知道MySQL是如何處理我們的SQL語句的。分析出查詢語句或是表結構的性能瓶頸。

1、MySQL查詢過程

MySQL之Explain性能分析

通過explain我們可以獲得以下資訊:

  • 表的讀取順序;
  • 資料讀取操作的操作類型;
  • 哪些索引可以被使用;
  • 哪些索引真正被使用;
  • 表的直接引用;
  • 每張表的有多少行被優化器查詢了;

Explain使用方式: explain+sql語句, 通過執行explain可以獲得sql語句執行的相關資訊。

explain select * from L1;           
MySQL之Explain性能分析

二、Explain 詳解

1) 資料準備

-- 建立資料庫 
CREATE DATABASE test_explain CHARACTER SET 'utf8'; 

-- 建立表 
CREATE TABLE L1(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L2(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) ); 
CREATE TABLE L3(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) ); 
CREATE TABLE L4(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) ); 

-- 每張表插入3條資料 
INSERT INTO L1(title) VALUES('zhang001'),('zhang002'),('zhang003'); 
INSERT INTO L2(title) VALUES('zhang004'),('zhang005'),('zhang006'); 
INSERT INTO L3(title) VALUES('zhang007'),('zhang008'),('zhang009'); 
INSERT INTO L4(title) VALUES('zhang010'),('zhang011'),('zhang012');           

2) ID字段說明

select查詢的序列号,包含一組數字,表示查詢中執行select子句或操作表的順序

id相同,執行順序由上至下

EXPLAIN SELECT * FROM L1,L2,L3 WHERE L1.id=L2.id AND L2.id = L3.id;           
MySQL之Explain性能分析

id不同,如果是子查詢,id的序号會遞增,id值越大優先級越高,越先被執行

EXPLAIN SELECT * FROM L2 WHERE id = ( SELECT id FROM L1 WHERE id = (SELECT L3.id FROM L3 WHERE L3.title = 'zhang'));           
MySQL之Explain性能分析

3) select_type和table字段說明

表示查詢類型,主要用于差別普通查詢,聯合查詢,子查詢等的複雜查詢

simple : 簡單的select查詢,查詢中不包含子查詢或者UNION

EXPLAIN SELECT * FROM L1 where id = 1;           
MySQL之Explain性能分析

primary : 查詢中若包含任何複雜的子部分,最外層查詢被标記

subquery : 在select或where清單中包含了子查詢

EXPLAIN SELECT * FROM L2 WHERE id = ( SELECT id FROM L1 WHERE id = (SELECT L3.id FROM L3 WHERE L3.title = 'zhang'));           
MySQL之Explain性能分析

union : union連接配接的兩個select查詢,第一個查詢是dervied派生表,除了第一個表外,第二個以後的表select_type都是union

derived : 在from清單中包含的子查詢被标記為derived(派生表),MySQL會遞歸執行這些子查詢,把結果放到臨時表中

union result : UNION 的結果

EXPLAIN SELECT * FROM (select * from L3 union select * from L4)a;           
MySQL之Explain性能分析

4) type字段說明

type字段在 MySQL 官網文檔描述如下:

The join type. For descriptions of the difffferent types.

type字段顯示的是連接配接類型 ( join type表示的是用什麼樣的方式來擷取資料),它描述了找到所需資料所使用的掃描方式, 是較為重要的一個名額。

下面給出各種連接配接類型,按照從最佳類型到最壞類型進行排序:

-- 完整的連接配接類型比較多 
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL 

-- 簡化之後,我們可以隻關注一下幾種 
system > const > eq_ref > ref > range > index > ALL           

一般來說,需要保證查詢至少達到 range級别,最好能到ref,否則就要就行SQL的優化調整

下面介紹type字段不同值表示的含義:

system: 表中就僅有一行資料的時候. 這是const連接配接類型的一個特例,很少出現。

const: const表示命中主鍵索引(primary key) 或者唯一索引(unique),表示通過索引一次就找到資料記錄。因為隻比對一條記錄,是以被連接配接的部分是一個常量。(如果将主鍵放在 where條件中, MySQL就能将該查詢轉換為一個常量) 這種類型非常快。

例如以下查詢:

explain select * from L1 where id = 3;

-- 為L1表的title字段添加唯一索引
alter table L1 add unique(title);

explain select * from L1 where title = 'zhang';           
MySQL之Explain性能分析

eq_ref : 對于前一個表中的每個一行,後表隻有一行被掃描。除了system和const類型之外,這是最好的連接配接類型。隻有當聯接使用索引的部分都是主鍵或惟一非空索引時,才會出現這種類型。

例如以下查詢:

EXPLAIN SELECT L1.id,L1.title FROM L1 left join L2 on L1.id = L2.id;           
MySQL之Explain性能分析

ref : 非唯一性索引掃描(使用了普通索引), 對于前表的每一行(row),後表可能有多于一行的資料被掃描,它傳回所有比對某個單獨值的行。

例如以下查詢:

-- 為L1表的title字段添加普通索引
alter table L1 add index idx_title (title) ;
EXPLAIN SELECT * FROM L1 inner join L2 on L1.title = L2.title;           
MySQL之Explain性能分析

range : 索引上的範圍查詢,檢索給定範圍的行,between,in函數,> 都是典型的範圍(range)查詢。

例如以下查詢:

EXPLAIN SELECT * FROM L1 WHERE L1.id between 1 and 10;           
MySQL之Explain性能分析

注: 當in函數中的資料很大時,可能會導緻效率下降,最終不走索引

index : 出現index 是 SQL 使用了索引, 但是沒有通過索引進行過濾,需要掃描索引上的全部資料 (查找所有索引樹,比ALL快一些,因為索引檔案要比資料檔案小 ), 一般是使用了索引進行排序分組。

EXPLAIN SELECT * FROM L2 group by id order by id;

-- 該count查詢需要通過掃描索引上的全部資料來計數
EXPLAIN SELECT count(*) FROM L2;           
MySQL之Explain性能分析

ALL : 沒有使用到任何索引, 連接配接查詢時對于前表的每一行,後表都要被全表掃描。

EXPLAIN SELECT * FROM L3 inner join L4 on L3.title = L4.title;           
MySQL之Explain性能分析

總結各類type類型的特點:

MySQL之Explain性能分析

5) possible_keys 與 key說明

possible_keys

顯示可能應用到這張表上的索引, 一個或者多個. 查詢涉及到的字段上若存在索引, 則該索引将被列出, 但不一定被查詢實際使用.

key

實際使用的索引,若為null,則沒有使用到索引。(兩種可能,1.沒建立索引, 2.建立索引,但索引失效)。查詢中若使用了覆寫索引,則該索引僅出現在key清單中。

1. 理論上沒有使用索引,但實際上使用了

EXPLAIN SELECT L3.id FROM L3;           
MySQL之Explain性能分析

2. 理論和實際上都沒有使用索引

EXPLAIN SELECT * FROM L3 WHERE title = 'zhang007';           
MySQL之Explain性能分析

3. 理論和實際上都使用了索引

EXPLAIN SELECT * FROM L2 WHERE title = 'zhang004';           
MySQL之Explain性能分析

6) key_len字段說明

表示索引中使用的位元組數, 可以通過該列計算查詢中使用索引的長度。

key_len 字段能夠幫你檢查是否充分利用了索引, ken_len 越長, 說明索引使用的越充分。

建立表

CREATE TABLE L5(
	a INT PRIMARY KEY,
	b INT NOT NULL,
	c INT DEFAULT NULL,
	d CHAR(10) NOT NULL
);           

使用explain 進行測試

EXPLAIN SELECT * FROM L5 WHERE a > 1 AND b = 1;           

觀察key_len的值, 索引中隻包含了1列 是int類型 ,是以,key_len是4位元組。

MySQL之Explain性能分析

為b字段添加索引,進行測試

ALTER TABLE L5 ADD INDEX idx_b(b);

-- 執行SQL,這次将b字段也作為條件
EXPLAIN SELECT * FROM L5 WHERE a > 1 AND b = 1;           
MySQL之Explain性能分析

為c、d字段添加聯合索引,然後進行測試

ALTER TABLE L5 ADD INDEX idx_c_b(c,d); 

explain select * from L5 where c = 1 and d = '';           
MySQL之Explain性能分析

c字段是int類型 4個位元組, d字段是 char(10)代表的是10個字元相當30個位元組

資料庫的字元集是utf8 一個字元3個位元組,d字段是 char(10)代表的是10個字元相當30個位元組,多出的一個位元組用來表示是聯合索引

下面這個例子中,雖然使用了聯合索引,但是可以根據ken_len的長度推測出該聯合索引隻使用了一部分,沒有充分利用索引,還有優化空間。

explain select * from L5 where c = 1 ;           
MySQL之Explain性能分析

7) ref 字段說明

顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用于查找索引列上的值

L1.id='1'; 1是常量 , ref = const

EXPLAIN SELECT * FROM L1 WHERE L1.id=1;           
MySQL之Explain性能分析

8) rows 字段說明

表示MySQL根據表統計資訊及索引選用情況,估算的找到所需的記錄所需要讀取的行數。

L3中的title沒有添加索引, 是以L3中有3條記錄,就需要讀取3條記錄進行查找。

EXPLAIN SELECT * FROM L3,L4 WHERE L3.id = L4.id AND L3.title LIKE 'zhang007';           
MySQL之Explain性能分析

需要注意的是 rows隻是一個估算值,并不準确 .是以rows行數過大的問題并不值得過多考慮,主要分析的還是索引是否使用正确了

9) fifiltered 字段說明

它指傳回結果的行占需要讀到的行(rows列的值)的百分比。

10) extra 字段說明

Extra 是 EXPLAIN 輸出中另外一個很重要的列,該列顯示MySQL在查詢過程中的一些詳細資訊。

準備資料

CREATE TABLE users (
uid INT PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR(20),
age INT(11)
);

INSERT INTO users VALUES(NULL, 'lisa',10);
INSERT INTO users VALUES(NULL, 'lisa',10);
INSERT INTO users VALUES(NULL, 'rose',11);
INSERT INTO users VALUES(NULL, 'jack', 12);
INSERT INTO users VALUES(NULL, 'sam', 13);           

Using fifilesort

執行結果Extra為 Using filesort ,這說明,得到所需結果集,需要對所有記錄進行檔案排序。這類SQL語句性能極差,需要進行優化。

典型的,在一個沒有建立索引的列上進行了order by,就會觸發fifilesort,常見的優化方案是,在order by的列上添加索引,避免每次查詢都全量排序。

EXPLAIN SELECT * FROM users ORDER BY age;           
MySQL之Explain性能分析

Using temporary

表示MySQL需要使用臨時表來存儲結果集,常見于排序和分組查詢

EXPLAIN SELECT COUNT(*),uname FROM users GROUP BY uname;           

需要注意的是:

  1. 傳回所有記錄的SQL,不使用where條件過濾資料,大機率不符合預期,對于這類SQL往往需要進行優化;
  2. 使用了where條件的SQL,并不代表不需要優化,往往需要配合explain結果中的type(連接配接類型)來綜合判斷。例如本例查詢的 age 未設定索引,是以傳回的type為ALL,仍有優化空間,可以建立索引優化查詢。
EXPLAIN SELECT * FROM users WHERE age=10;           
MySQL之Explain性能分析

Using index

表示直接通路索引就能夠擷取到所需要的資料(覆寫索引) , 不需要通過索引回表。

-- 為uname建立索引
alter table users add index idx_uname(uname);
EXPLAIN SELECT uid,uname FROM users WHERE uname='lisa';           
MySQL之Explain性能分析

Using join buffffer

使用了連接配接緩存, 會顯示join連接配接查詢時,MySQL選擇的查詢算法。

EXPLAIN SELECT * FROM users u1 LEFT JOIN (SELECT * FROM users WHERE age = 1)u2 ON u1.age = u2.age;           
MySQL之Explain性能分析

Using join buffer (Block Nested Loop) 說明,需要進行嵌套循環計算, 這裡每個表都有五

條記錄,内外表查詢的type都為ALL。

問題在于 兩個關聯表的關聯使用了字段 age,并且age字段未建立索引,就會出現這種情況。

Using index condition

查找使用了索引 (但是隻使用了一部分,一般是指聯合索引),但是需要回表查詢數.

explain select * from L5 where c > 10 and d = '';           
MySQL之Explain性能分析

Extra主要名額的含義(有時會同時出現)

  • using index :使用覆寫索引的時候就會出現;
  • using where :在查找使用索引的情況下,需要回表去查詢所需的資料;
  • using index condition :查找使用了索引,但是需要回表查詢資料;
  • using index & using where :查找使用了索引,但是需要的資料都在索引列中能找到,是以不需要回表查詢資料;

繼續閱讀