程序員必備接口測試調試工具:立即使用
Apipost = Postman + Swagger + Mock + Jmeter
Api設計、調試、文檔、自動化測試工具
后端、前端、測試,同時在線協(xié)作,內容實時同步
通常查詢慢查詢SQL
語句時會使用EXPLAIN
命令來查看SQL
語句的執(zhí)行計劃,通過返回的信息,可以了解到Mysql
優(yōu)化器是如何執(zhí)行SQL
語句,通過分析可以幫助我們提供優(yōu)化的思路。
1. Explain 作用
explain 命令主要用于查看 SQL 語句的執(zhí)行計劃,該命令可以模擬優(yōu)化器執(zhí)行 SQL 查詢語句,可以幫助我們編寫和優(yōu)化 SQL。那么 explain 具體可以提供哪些信息,幫助我們如何去優(yōu)化 SQl 的呢?
-
表的讀取順序
-
數(shù)據(jù)讀取操作的操作類型
-
哪些索引可以使用
-
哪些索引被實際使用
-
表之間的引用
-
每張表有多少行被優(yōu)化器查詢
2. Explain 如何使用
使用方式: explain + 待執(zhí)行的sql
explain 會返回一個待執(zhí)行 SQL 的執(zhí)行計劃列表,列表包含了 12 個字段,字段共同描述了 SQL 在執(zhí)行計劃中將會采取何種方式執(zhí)行。以下列表詳細描述了執(zhí)行計劃表的字段含義:
字段名稱 | 描述 |
---|---|
id | 執(zhí)行 select 語句查詢的序列號,決定表的讀取順序 |
select_type | 查詢的類型,也就是數(shù)據(jù)讀取操作的操作類型 |
table | 查詢的表名 |
partitions | 表分區(qū) |
type | 訪問類型 |
possible_keys | 可使用的索引。查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢實際使用到。如果這個字段為 null 但是字段 key 不為 null,這種情況就是在查找時沒有可以使用的二級索引樹,但是二級索引中包含了需要查詢的字段,于是就不再查找聚簇索引(聚簇索引比較大),轉而掃描這個二級索引樹(二級索引樹比較?。?,并且此時一般訪問類型 type 為 index,及掃描整棵索引樹。 |
key | 實際掃描使用的索引。如果為 null,則沒有使用索引;查詢中若使用了覆蓋索引,則該索引僅出現(xiàn)在key列表中; |
key_len | 索引中使用的字節(jié)數(shù)。可通過該列計算查詢中使用的索引的長度,在不損失精確性的情況下,長度越短越好;key_len 顯示的值為索引字段的最大可能長度,并非實際使用長度,即 key_len 是根據(jù)表定義計算而得,不是通過表內檢索出的; |
ref | 顯示索引的哪一列被使用了。如果可能的話,是一個常數(shù),哪些列或常量別用于查找索引列上的值; |
rows | 根據(jù)表統(tǒng)計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數(shù); |
filtered | 搜索條件過濾后剩余數(shù)據(jù)的百分比。 |
Extra | 包含不適合在其它列中顯示但十分重要的額外信息 |
3. 關鍵字段分析
(1)id
執(zhí)行 select 語句查詢的序列號,包含一組數(shù)字,表示查詢中執(zhí)行 select 子句或操作表的順序,它有三種情況:
類型名稱 | 描述 |
---|---|
id相同 | 執(zhí)行順序由上至下 |
id不同 | 如果是子查詢,id 的序號會遞增,id 值越大優(yōu)先級越高,越先被執(zhí)行 |
id相同不同,同時存在 | 如果 id 相同,可以認為是一組,從上往下順序執(zhí)行,在所有組中,id值越大,優(yōu)先級越高,越先執(zhí)行 |
(2)select_type
就是數(shù)據(jù)讀取操作的操作類型,他一共有以下幾種:
類型名稱 | 描述 |
---|---|
simple | 簡單的 select 查詢,查詢中不包含子查詢或者 union; |
primary | 查詢中若包含任何復雜的子查詢,最外層查詢則被標記; |
subquery | 在 select 或者 where 列表中包含了子查詢; |
dependent subquery | 子查詢中的第一個 SELECT, 取決于外面的查詢。 即子查詢依賴于外層查詢的結果。 |
derived | 在 from 列表中包含的子查詢被標記為 DERIVED(衍生表),mysql 會遞歸執(zhí)行這些子查詢,把結果放臨時表中; |
union | 若第二個 select 出現(xiàn)在 union 之后,則被標記為 union,若 union 包含在 from 子句的子查詢中,外層 select 將被標記為 DERIVED; |
union result | 從 union 表(即 union 合并的結果集)中獲取 select 查詢的結果; |
meterialized | 物化表,子查詢關聯(lián)查詢時,子查詢結果存儲在物化臨時表,然后根據(jù)臨時表中的數(shù)據(jù)去主表匹配。 |
dependent union | UNION 中的第二個或后面的查詢語句,取決于外面的查詢 |
(3)table
顯示的查詢表名,如果查詢使用了別名,那么這里顯示的是別名,如果不涉及對數(shù)據(jù)表的操作,那么這顯示為 null,也可以是以下之一:
類型名稱 | 描述 |
---|---|
<derivedN > |
表示這個是臨時表,后邊的N就是執(zhí)行計劃中的 id,表示結果來自于這個查詢產(chǎn)生。 |
<unionM ,N > |
與<derivedN >類似,也是一個臨時表,表示這個結果來自于 union 查詢的 id 為 M,N 的結果集。 |
<subqueryN > |
該行是指與物化子查詢該行的結果 id 的值 N。 |
(4)partitions
查詢將匹配記錄的分區(qū)。該值NULL
用于非分區(qū)表。
(5)type
依次從好到差:
system
>const
>eq_ref
>ref
>ref_or_null
>range
>index
>ALL
除了all
之外,其他的type
都可以使用到索引,除了index_merge
之外,其他的type
只可以用到一個索引。
我們自己創(chuàng)建一系列表來實驗下:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for goods -- ---------------------------- DROP TABLE IF EXISTS `goods`; CREATE TABLE `goods` ( `id` int(11) NOT NULL, `sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of goods -- ---------------------------- INSERT INTO `goods` VALUES (1, 'sn123456', '衣服'); -- ---------------------------- -- Table structure for sku -- ---------------------------- DROP TABLE IF EXISTS `sku`; CREATE TABLE `sku` ( `id` int(11) NOT NULL, `goods_id` int(11) NOT NULL, `status` int(11) NOT NULL, `deleted` int(11) NOT NULL, `barcode` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `index_2`(`name`) USING BTREE, INDEX `index_1`(`goods_id`, `status`, `deleted`, `barcode`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of sku -- ---------------------------- INSERT INTO `sku` VALUES (1, 1, 1, 0, 'kt123456', '黑色'); SET FOREIGN_KEY_CHECKS = 1;
system
表只有一行記錄(等于系統(tǒng)表),這是 const 類型的特例,平時不會出現(xiàn),這個也可忽略不計;
const
表示通過索引一次就找到了,const 用于比較 primary key 或者 unique 索引。因為只匹配一行記錄,所以很快。 如果將主鍵置于 where 列表中,mysql 就能將該查詢轉換成一個常量;
EXPLAIN SELECT * FROM sku WHERE id=1;復制代碼
eq_ref
唯一性索引掃描,對于每一個索引鍵,表中只有一條記錄與之匹配,常用于主鍵或唯一索引掃描;此類型通常出現(xiàn)在多表的 join 等值查詢,表示對于前表的每一個結果,都只能匹配到后表的一行結果,查詢效率較高。
EXPLAIN SELECT * FROM sku,goods WHERE sku.goods_id=goods.id;
ref
非唯一性索引掃描,返回匹配某個單獨值得所有行,本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,所以它應該屬于查找和掃描的混合體;
EXPLAIN SELECT * FROM sku WHERE goods_id=1;
ref_or_null
二級索引等值比較同時限定 is null 。
EXPLAIN SELECT * FROM sku WHERE name='123456' or name IS NULL;
range
只檢索給定范圍的行,使用一個索引來選擇行,key列顯示使用哪個索引,一般就是在你的 where 語句中出現(xiàn)了 between、<、>、in 等的查詢;這種范圍索引掃描比全表掃描要好,因為它只需要開始于索引的某一個點,結束于另一個點,不用掃描全部索引;
EXPLAIN SELECT * FROM sku WHERE id BETWEEN 1 and 10;
index
index 和 all 區(qū)別為 index 類型只遍歷索引樹,這通常比 all 快,因為索引文件通常比數(shù)據(jù)文件?。灰簿褪钦f雖然 all 和 index 都是讀寫表,但 index 是從索引中讀取的,而 all 是從硬盤中讀的;
EXPLAIN SELECT barcode FROM sku WHERE deleted=0;
all
也就是全表掃描;
EXPLAIN SELECT * FROM sku WHERE deleted=0;
(6)possible_keys
查詢可能使用到的索引都會在這里列出來。
(7)key
查詢真正使用到的索引,select_type
為index_merge
時,這里可能出現(xiàn)兩個以上的索引,其他的select_type
這里只會出現(xiàn)一個。
(8)key_len
key_len 表示該列計算查詢中使用的索引的長度。例如:SELECT * FROM table where age = 1 and name like 'xx'
,假設 age 是 int 類型且不可為 null;name 是 varchar(20) 類型且可以為 null,編碼為 utf8。若以這兩個字段為索引查詢,那么 key_len 的值為 4 + 3 * 20 + 2 + 1 = 67
。具體計算規(guī)則如下表所示:
值類型 | 值名稱 | 描述 |
---|---|---|
字符串 | CHAR(n) | n 字節(jié)長度 |
VARCHAR(n) | 如果是 utf8 編碼,則是 3 n + 2字節(jié);;如果是 utf8mb4 編碼,則是 4 n + 2 字節(jié)。 | |
數(shù)值類型 | TINYINT | 1字節(jié) |
SMALLINT | 2字節(jié) | |
MEDIUMINT | 3字節(jié) | |
INT | 4字節(jié) | |
BIGINT | 8字節(jié) | |
時間類型 | DATE | 3字節(jié) |
TIMESTAMP | 4字節(jié) | |
DATETIME | 8字節(jié) | |
字段屬性 | NULL 屬性 占用一個字節(jié)。如果一個字段是 NOT NULL 的, 則不占用。 |
(9)ref
如果是使用的常數(shù)等值查詢,這里會顯示const
,如果是連接查詢,被驅動表的執(zhí)行計劃這里會顯示驅動表的關聯(lián)字段,如果是條件使用了表達式或者函數(shù),或者條件列發(fā)生了內部隱式轉換,這里可能顯示為func
。
(10)rows
這里是執(zhí)行計劃中估算的掃描行數(shù),不是精確值。
(11)filtered
使用explain extended
時會出現(xiàn)這個列,5.7
之后的版本默認就有這個字段,不需要使用explain extended
了。這個字段表示存儲引擎返回的數(shù)據(jù)在server層過濾后,剩下多少滿足查詢的記錄數(shù)量的比例,注意是百分比,不是具體記錄數(shù)。
(12)Extra
這個列可以顯示的信息非常多,有幾十種,常用的有:
1、distinct:在select
部分使用了distinct
關鍵字
2、no tables used:不帶from
字句的查詢或者From dual
查詢。使用not in()
形式子查詢或not exists()
運算符的連接查詢,這種叫做反連接。即,一般連接查詢是先查詢內表,再查詢外表,反連接就是先查詢外表,再查詢內表。
3、using filesort:說明mysql
會對數(shù)據(jù)使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。mysql
中無法利用索引完成的排序操作稱為“文件排序”。排序時無法使用到索引時,就會出現(xiàn)這個。常見于order by
語句中,需要盡快優(yōu)化
4、using index:查詢時不需要回表查詢,直接通過索引就可以獲取查詢的數(shù)據(jù)。
5、using join buffer(block nested loop),using join buffer(batched key accss) :5.6.x
之后的版本優(yōu)化關聯(lián)查詢的BNL
,BKA
特性。主要是減少內表的循環(huán)數(shù)量以及比較順序地掃描查詢。
6、using sort_union,using_union,using intersect,using sort_intersection:
- using intersect:表示使用
and
的各個索引的條件時,該信息表示是從處理結果獲取交集 - using union:表示使用
or
連接各個使用索引的條件時,該信息表示從處理結果獲取并集 - using sort_union和using sort_intersection:與前面兩個對應的類似,只是他們是出現(xiàn)在用
and
和or
查詢信息量大時,先查詢主鍵,然后進行排序合并后,才能讀取記錄并返回。
7、using temporary:表示使用了臨時表存儲中間結果。臨時表可以是內存臨時表和磁盤臨時表,執(zhí)行計劃中看不出來,需要查看status
變量,used_tmp_table
,used_tmp_disk_table
才能看出來。常見于order by
和分組查詢group by
。group by
一定要遵循所建索引的順序與個數(shù)。需要盡快優(yōu)化
8、using where:表示存儲引擎返回的記錄并不是所有的都滿足查詢條件,需要在server
層進行過濾。查詢條件中分為限制條件和檢查條件,5.6
之前,存儲引擎只能根據(jù)限制條件掃描數(shù)據(jù)并返回,然后server
層根據(jù)檢查條件進行過濾再返回真正符合查詢的數(shù)據(jù)。5.6.x
之后支持ICP
特性(index condition pushdown,索引下推),可以把檢查條件也下推到存儲引擎層,不符合檢查條件和限制條件的數(shù)據(jù),直接不讀取,這樣就大大減少了存儲引擎掃描的記錄數(shù)量。extra
列顯示using index condition
9、firstmatch(tb_name) :5.6.x
開始引入的優(yōu)化子查詢的新特性之一,常見于where
字句含有in()
類型的子查詢。如果內表的數(shù)據(jù)量比較大,就可能出現(xiàn)這個
10、loosescan(m..n) :5.6.x
之后引入的優(yōu)化子查詢的新特性之一,在in()
類型的子查詢中,子查詢返回的可能有重復記錄時,就可能出現(xiàn)這個
4. Explain 主要關注點
總的來說,我們只需要關注結果中的幾列:
列名 | 備注 |
---|---|
type | 本次查詢表聯(lián)接類型,從這里可以看到本次查詢大概的效率 |
key | 最終選擇的索引,如果沒有索引的話,本次查詢效率通常很差 |
key_len | 本次查詢用于結果過濾的索引實際長度 |
rows | 預計需要掃描的記錄數(shù),預計需要掃描的記錄數(shù)越小越好 |
Extra | 額外附加信息,主要確認是否出現(xiàn)Using filesort 、Using temporary 這兩種情況 |
再來看下Extra
列中需要注意出現(xiàn)的幾種情況:
關鍵字 | 備注 |
---|---|
Using filesort | 將用外部排序而不是按照索引順序排列結果,數(shù)據(jù)較少時從內存排序,否則需要在磁盤完成排序,代價非常高,需要添加合適的索引 |
Using temporary | 需要創(chuàng)建一個臨時表來存儲結果,這通常發(fā)生在對沒有索引的列進行GROUP BY 時,或者ORDER BY 里的列不都在索引里,需要添加合適的索引 |
Using index | 表示MySQL 使用覆蓋索引避免全表掃描,不需要再到表中進行二次查找數(shù)據(jù),這是比較好的結果之一。注意不要和type 中的index 類型混淆 |
Using where | 通常是進行了全表/全索引掃描后再用WHERE 子句完成結果過濾,需要添加合適的索引 |
Impossible WHERE | 對Where 子句判斷的結果總是false而不能選擇任何數(shù)據(jù),例如where 1=0 ,無需過多關注 |
Select tables optimized away | 使用某些聚合函數(shù)來訪問存在索引的某個字段時,優(yōu)化器會通過索引直接一次定位到所需要的數(shù)據(jù)行完成整個查詢,例如MIN()MAX() ,這種也是比較好的結果之一 |
【