一鞍历、前言
上周老周的一個好朋友讓我出一篇教你讀懂 SQL 執(zhí)行計劃,和我另一位讀者反饋的面試題如何排查慢 SQL 的強相關(guān),索性先出一篇一文讀懂 MySQL Explain 執(zhí)行計劃。Explain 執(zhí)行計劃你一定得會看,不然你簡歷上就別去寫什么你會 SQL 調(diào)優(yōu)谱俭,不然面試官會覺得,Explain 執(zhí)行計劃你都不會看宵蛀,那你還 SQL 調(diào)啥優(yōu)袄ブ?SQL 調(diào)你吧术陶?凑懂??開個小玩笑梧宫,玩笑歸玩笑接谨,重要是真的重要!L料弧脓豪!
二、Explain 執(zhí)行計劃是什么忌卤?
什么是執(zhí)行計劃扫夜?簡而言之,就是 SQL 在數(shù)據(jù)庫中執(zhí)行時的表現(xiàn)情況驰徊,通常用于 SQL 性能分析笤闯、優(yōu)化和加鎖分析等場景,執(zhí)行過程會在 MySQL 查詢過程中由解析器棍厂,預(yù)處理器和查詢優(yōu)化器共同生成颗味。在 MySQL 中使用 explain 關(guān)鍵字來查看。
2.1 執(zhí)行計劃有什么用牺弹?
它可以用來分析 SQL 語句和表結(jié)構(gòu)的性能瓶頸
- 關(guān)聯(lián)查詢的執(zhí)行順序
- 查詢操作的操作類型
- 哪些索引可以被命中
- 哪些索引實際被命中
- 每張表有多少記錄參與查詢
- ...
2.2 MySQL 執(zhí)行過程
[圖片上傳失敗...(image-ea6abe-1649170227750)]
如上圖所示浦马,MySQL 數(shù)據(jù)庫由 Server 層和 Engine 層組成:
- Server 層有 SQL 分析器时呀、SQL優(yōu)化器、SQL 執(zhí)行器捐韩,用于負(fù)責(zé) SQL 語句的具體執(zhí)行過程退唠;
- Engine 層負(fù)責(zé)存儲具體的數(shù)據(jù),如最常使用的 MyISAM荤胁、InnoDB 存儲引擎,還有用于在內(nèi)存中存儲臨時結(jié)果集的 TempTable 引擎屎债。
SQL 優(yōu)化器會分析所有可能的執(zhí)行計劃仅政,選擇成本最低的執(zhí)行,這種優(yōu)化器稱之為:CBO(Cost-based Optimizer盆驹,基于成本的優(yōu)化器)圆丹。
而在 MySQL 中,一條 SQL 的計算成本計算如下所示:
Cost = Server Cost + Engine Cost
= CPU Cost + IO Cost
其中躯喇,CPU Cost 表示計算的開銷辫封,比如索引鍵值的比較、記錄值的比較廉丽、結(jié)果集的排序等這些操作都在 Server 層完成倦微;
IO Cost 表示引擎層 IO 的開銷,MySQL 8.0 可以通過區(qū)分一張表的數(shù)據(jù)是否在內(nèi)存中正压,分別計算讀取內(nèi)存 IO 開銷以及讀取磁盤 IO 的開銷欣福。
數(shù)據(jù)庫 mysql 下的表 server_cost、engine_cost 則記錄了對于各種成本的計算焦履,如:
[圖片上傳失敗...(image-a029e1-1649170227751)]
表 server_cost 記錄了 Server 層優(yōu)化器各種操作的成本拓劝,這里面包括了所有 CPU Cost,其具體含義如下:
-
disk_temptable_create_cost
:創(chuàng)建磁盤臨時表的成本嘉裤,默認(rèn)為20
郑临。 -
disk_temptable_row_cost
:磁盤臨時表中每條記錄的成本,默認(rèn)為0.5
屑宠。 -
key_compare_cost
:索引鍵值比較的成本厢洞,默認(rèn)為0.05
,成本最小侨把。 -
memory_temptable_create_cost
:創(chuàng)建內(nèi)存臨時表的成本:默認(rèn)為1
犀变。 -
memory_temptable_row_cost
:內(nèi)存臨時表中每條記錄的成本,默認(rèn)為0.1
秋柄。 -
row_evaluate_cost
:記錄間的比較成本获枝,默認(rèn)為0.1
。
可以看到骇笔, MySQL 優(yōu)化器認(rèn)為如果一條 SQL 需要創(chuàng)建基于磁盤的臨時表省店,則這時的成本是最大的嚣崭,其成本是基于內(nèi)存臨時表的 20 倍。而索引鍵值的比較懦傍、記錄之間的比較雹舀,其實開銷是非常低的,但如果要比較的記錄數(shù)非常多粗俱,則成本會變得非常大说榆。
而表 engine_cost 記錄了存儲引擎層各種操作的成本,這里包含了所有的 IO Cost寸认,具體含義如下:
-
io_block_read_cost
:從磁盤讀取一個頁的成本签财,默認(rèn)值為1
。 -
memory_block_read_cost
:從內(nèi)存讀取一個頁的成本偏塞,默認(rèn)值為0.25
唱蒸。
也就是說, MySQL 優(yōu)化器認(rèn)為從磁盤讀取的開銷是內(nèi)存開銷的 4 倍灸叼。
三神汹、Explain 執(zhí)行計劃詳解
我們先來準(zhǔn)備以下 SQL 腳本:
CREATE TABLE `user` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR (20) DEFAULT NULL COMMENT "用戶名",
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "用戶表";
CREATE TABLE `user_robot_relate` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`user_id` INT (11) NOT NULL COMMENT "用戶id",
`robot_id` INT (11) NOT NULL COMMENT "機器人id",
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_robot_id` (`robot_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "用戶與機器人表";
CREATE TABLE `robot` (
`id` INT (11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR ( 20 ) DEFAULT NULL COMMENT "機器人名",
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "機器人表";
INSERT INTO user VALUES (1, 'riemann');
INSERT INTO user VALUES (2, 'andy');
INSERT INTO user_robot_relate VALUES (1, 1, 1);
INSERT INTO user_robot_relate VALUES (2, 1, 2);
INSERT INTO user_robot_relate VALUES (3, 2, 3);
INSERT INTO robot VALUES (1, '小白鯨');
INSERT INTO robot VALUES (2, '掃地機');
INSERT INTO robot VALUES (3, '掃拖一體機');
我們創(chuàng)建三張表 user、user_robot_relate古今、robot屁魏,表之間的關(guān)系 user.id = user_robot_relate.user_id AND user_robot_relate.robot_id = robot.id。
先來看下我的 MySQL 版本沧卢,是 5.7.37 的蚁堤。
[圖片上傳失敗...(image-50b394-1649170227751)]
接著我們看一下執(zhí)行計劃有哪些字段,先看個整體的但狭,讓大家有個大概的認(rèn)識后披诗,我們再逐一去詳解分析。
[圖片上傳失敗...(image-669021-1649170227751)]
explain 執(zhí)行后輸出的結(jié)果集包含 12 列立磁,分別是 id呈队、select_type、table唱歧、partitions宪摧、type、possible_keys颅崩、key几于、key_len、ref沿后、rows沿彭、filtered 和 Extra,下面對這些字段進行解釋尖滚。
-
id
:Query Optimizer 所選定的執(zhí)行計劃中查詢的序列號 -
select_type
:顯示本行是簡單或復(fù)雜 select喉刘。如果查詢有任何復(fù)雜的子查詢瞧柔,則最外層標(biāo)記為PRIMARY、DERIVED.睦裳、UNION造锅、UNION RESUIT 等。 -
table
:顯示這一步所訪問的數(shù)據(jù)庫中的表的名稱 -
partitions
:查詢時匹配到的分區(qū)信息廉邑,對于非分區(qū)表值為 NULL哥蔚,當(dāng)查詢的是分區(qū)表時,partitions 顯示分區(qū)表命中的分區(qū)情況鬓催。 -
type
:數(shù)據(jù)訪問肺素、讀取操作類型(ALL、index宇驾、range、ref猴伶、eq_ref课舍、const、system)等 -
possible_keys
:該查詢可以利用的索引他挎,如果沒有任何索引可以使用筝尾,就會顯示成 null,這一
項內(nèi)容對于優(yōu)化時候索引的調(diào)整非常重要办桨。 -
key
:MySQL Query Optimizer 從 possible_keys 中所選擇使用的索引 -
key_len
:被選中使用索引的索引鍵長度 -
ref
:列出是通過常量(const)筹淫,還是某個表的某個字段(如果是 join)來過濾(通過 key)
的。 -
rows
:MySQL Query Optimizer 通過系統(tǒng)收集到的統(tǒng)計信息估算出來的結(jié)果集記錄條數(shù) -
filtered
:表示存儲引擎返回的數(shù)據(jù)在經(jīng)過過濾后呢撞,剩下滿足條件的記錄數(shù)量的比例损姜。 -
Extra
:查詢中每一步實現(xiàn)的額外細(xì)節(jié)信息,如 Using filesort殊霞、index 等摧阅。
3.1 id
看到三條記錄的 id 都相同,可以理解成這三個表為一組绷蹲,具有同樣的優(yōu)先級棒卷,執(zhí)行順序由上而下,具體順序由優(yōu)化器決定祝钢。
3.1.1 id 相同
mysql> EXPLAIN SELECT * FROM user u WHERE u.id = (SELECT ur.user_id FROM user_robot_relate ur WHERE ur.robot_id = (SELECT r.id FROM robot r WHERE r.name = '掃地機'));
+----+-------------+-------+------------+------+--------------------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+--------------------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | ur | NULL | ALL | idx_user_id,idx_robot_id | NULL | NULL | NULL | 3 | 100.00 | NULL |
| 1 | SIMPLE | u | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
| 1 | SIMPLE | r | NULL | ALL | PRIMARY | NULL | NULL | NULL | 3 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+--------------------------+------+---------+------+------+----------+----------------------------------------------------+
3.1.2 id 不同
如果我們的 SQL 中存在子查詢比规,那么 id 的序號會遞增,id 值越大優(yōu)先級越高拦英,越先被執(zhí)行蜒什。當(dāng)三個表依次嵌套,發(fā)現(xiàn)最里層的子查詢 id 最大龄章,最先執(zhí)行吃谣。
mysql> EXPLAIN SELECT * FROM user u WHERE u.id = (SELECT ur.user_id FROM user_robot_relate ur WHERE ur.robot_id = (SELECT r.id FROM robot r WHERE r.name = '掃地機'));
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | u | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | ur | NULL | ref | idx_robot_id | idx_robot_id | 4 | const | 1 | 100.00 | Using where |
| 3 | SUBQUERY | r | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
3.1.3 以上兩種同時存在
將上邊的 SQL 稍微修改一下乞封,增加一個子查詢,發(fā)現(xiàn) id 的以上兩種同時存在岗憋。相同 id 劃分為一組肃晚,這樣就有三個組,同組的從上往下順序執(zhí)行仔戈,不同組 id 值越大关串,優(yōu)先級越高,越先執(zhí)行监徘。
mysql> EXPLAIN SELECT * FROM user u WHERE u.id = (SELECT ur.user_id FROM user_robot_relate ur WHERE ur.robot_id = (SELECT r.id FROM robot r WHERE r.name = '掃地機')) AND u.id IN (SELECT u.id FROM user u WHERE u.name = 'riemann');
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | u | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 1 | PRIMARY | u | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | ur | NULL | ref | idx_robot_id | idx_robot_id | 4 | const | 1 | 100.00 | Using where |
| 3 | SUBQUERY | r | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+-------+------------+-------+---------------+--------------+---------+-------+------+----------+-------------+
3.2 select_type
select_type:表示 select 查詢的類型晋修,主要是用于區(qū)分各種復(fù)雜的查詢,例如:普通查詢凰盔、聯(lián)合查詢墓卦、子查詢等。
SIMPLE
:表示最簡單的 select 查詢語句户敬,也就是在查詢中不包含子查詢或者 union 交并差集等操作落剪。PRIMARY
:當(dāng)查詢語句中包含任何復(fù)雜的子部分,最外層查詢則被標(biāo)記為 PRIMARY尿庐。SUBQUERY
:當(dāng) select 或 where 列表中包含了子查詢忠怖,該子查詢被標(biāo)記為 SUBQUERY。DERIVED
:表示包含在 from 子句中的子查詢的 select抄瑟,在我們的 from 列表中包含的子查詢會被標(biāo)記為 derived凡泣。UNION
:如果 union 后邊又出現(xiàn)的 select 語句,則會被標(biāo)記為 union皮假;若 union 包含在 from 子句的子查詢中鞋拟,外層 select 將被標(biāo)記為 derived。-
UNION RESULT
:代表從 union 的臨時表中讀取數(shù)據(jù)钞翔,而 table 列的 <union1,4> 表示用第一個和第四個 select 的結(jié)果進行 union 操作严卖。mysql> EXPLAIN SELECT t.user_id, (SELECT u.id FROM user u) o FROM (SELECT ur.user_id, ur.robot_id FROM user_robot_relate ur WHERE ur.id = 2) t UNION (SELECT r.id, r.name FROM robot r); +----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+ | 1 | PRIMARY | ur | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL | | 2 | SUBQUERY | u | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index | | 4 | UNION | r | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL | | NULL | UNION RESULT | <union1,4> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------+------------+------------+-------+---------------+---------+---------+-------+------+----------+-----------------+
3.3 table
查詢的表名,并不一定是真實存在的表布轿,有別名顯示別名哮笆,也可能為臨時表,例如上邊的 DERIVED汰扭、 <union1,4> 等稠肘。
3.4 partitions
查詢時匹配到的分區(qū)信息,對于非分區(qū)表值為 NULL萝毛,當(dāng)查詢的是分區(qū)表時项阴,partitions 顯示分區(qū)表命中的分區(qū)情況。
3.5 type
type:查詢使用了何種類型,它在 SQL優(yōu)化中是一個非常重要的指標(biāo)环揽,以下性能從好到壞依次是:system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system
:當(dāng)表僅有一行記錄時(系統(tǒng)表)略荡,數(shù)據(jù)量很少,往往不需要進行磁盤 IO歉胶,速度非逞炊担快。-
const
:表示查詢時命中 primary key 主鍵或者 unique 唯一索引通今,或者被連接的部分是一個常量(const)值粥谬。這類掃描效率極高,返回數(shù)據(jù)量少辫塌,速度非陈┎撸快。mysql> EXPLAIN SELECT * FROM robot WHERE id = 1; +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | robot | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
-
eq_ref
:查詢時命中主鍵 primary key 或者 unique key 索引臼氨, type 就是 eq_ref掺喻。mysql> EXPLAIN SELECT u.name FROM user u, user_robot_relate ur WHERE u.id = ur.id; +----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+ | 1 | SIMPLE | u | NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 100.00 | NULL | | 1 | SIMPLE | ur | NULL | eq_ref | PRIMARY | PRIMARY | 4 | test.u.id | 1 | 100.00 | Using index | +----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+
-
ref
:區(qū)別于 eq_ref,ref 表示使用非唯一性索引储矩,會找到很多個符合條件的行巢寡。mysql> EXPLAIN SELECT id FROM user_robot_relate WHERE user_id = 2; +----+-------------+-------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | user_robot_relate | NULL | ref | idx_user_id | idx_user_id | 4 | const | 1 | 100.00 | Using index | +----+-------------+-------------------+------------+------+---------------+-------------+---------+-------+------+----------+-------------+
-
ref_or_null
:這種連接類型類似于 ref,區(qū)別在于 MySQL 會額外搜索包含 NULL 值的行椰苟。-- 為了模擬這個場景,我又新增了一個 user_test 表树叽。 mysql> CREATE TABLE `user_test` ( -> `id` INT (11) NOT NULL AUTO_INCREMENT, -> `name` VARCHAR (20) DEFAULT NULL COMMENT "用戶名", -> PRIMARY KEY (`id`), -> KEY `idx_name` (`name`) -> ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = "用戶測試表"; Query OK, 0 rows affected (0.01 sec) mysql> EXPLAIN SELECT id FROM user_test WHERE name = 'riemann' OR name IS NULL; +----+-------------+-----------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+ | 1 | SIMPLE | user_test | NULL | ref_or_null | idx_name | idx_name | 83 | const | 2 | 100.00 | Using where; Using index | +----+-------------+-----------+------------+-------------+---------------+----------+---------+-------+------+----------+--------------------------+
-
index_merge
:使用了索引合并優(yōu)化方法舆蝴,查詢使用了兩個以上的索引。-- 下邊示例中同時使用到主鍵 id 和字段 user_id 的索引题诵。 mysql> EXPLAIN SELECT * FROM user_robot_relate WHERE id > 1 AND user_id = 2; +----+-------------+-------------------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+ | 1 | SIMPLE | user_robot_relate | NULL | index_merge | PRIMARY,idx_user_id | idx_user_id,PRIMARY | 8,4 | NULL | 1 | 100.00 | Using intersect(idx_user_id,PRIMARY); Using where | +----+-------------+-------------------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
-
unique_subquery
:替換下面的 IN 子查詢洁仗,子查詢返回不重復(fù)的集合。value IN (SELECT primary_key FROM single_table WHERE some_expr)
-
index_subquery
:區(qū)別于 unique_subquery性锭,用于非唯一索引赠潦,可以返回重復(fù)值。value IN (SELECT key_column FROM single_table WHERE some_expr)
-
range
:使用索引選擇行草冈,僅檢索給定范圍內(nèi)的行烤送。簡單點說就是針對一個有索引的字段蟆豫,給定范圍檢索數(shù)據(jù)。在 where 語句中使用 bettween...and、<好渠、>、<=终畅、in 等條件查詢 type 都是 range渴肉。-- user_robot_relate 表中 id 為唯一主鍵,name 普通字段未建索引谬运。 mysql> EXPLAIN SELECT * FROM user_robot_relate WHERE id BETWEEN 2 AND 3; +----+-------------+-------------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | user_robot_relate | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where | +----+-------------+-------------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
從結(jié)果中看到只有對設(shè)置了索引的字段隙赁,做范圍檢索 type 才是 range垦藏。
mysql> EXPLAIN SELECT * FROM user WHERE name BETWEEN 2 AND 3; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
-
index
:Index 與 ALL 其實都是讀全表,區(qū)別在于 index 是遍歷索引樹讀取伞访,而 ALL 是從硬盤中讀取掂骏。-- id 為主鍵,不帶 where 條件全表查詢咐扭,type 結(jié)果為 index芭挽。 mysql> EXPLAIN SELECT id FROM robot; +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | robot | NULL | index | NULL | PRIMARY | 4 | NULL | 3 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
-
ALL
:將遍歷全表以找到匹配的行,性能最差蝗肪。mysql> EXPLAIN SELECT * FROM robot; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | SIMPLE | robot | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
3.6 possible_keys
possible_keys
:表示在 MySQL 中通過哪些索引袜爪,能讓我們在表中找到想要的記錄,一旦查詢涉及到的某個字段上存在索引薛闪,則索引將被列出辛馆,但這個索引并不定一會是最終查詢數(shù)據(jù)時所被用到的索引。具體請參考上邊的例子豁延。
3.7 key
key
:區(qū)別于 possible_keys昙篙,key 是查詢中實際使用到的索引,若沒有使用索引诱咏,顯示為 NULL苔可。具體請參考上邊的例子。
當(dāng) type 為 index_merge 時袋狞,可能會顯示多個索引焚辅。
3.8 key_len
key_len:表示查詢用到的索引長度(字節(jié)數(shù)),原則上長度越短越好 苟鸯。
- 單列索引同蜻,那么需要將整個索引長度算進去;
- 多列索引早处,不是所有列都能用到湾蔓,需要計算查詢中實際用到的列。
注:key_len 只計算 where 條件中用到的索引長度砌梆,而排序和分組即便是用到了索引默责,也不會計算到 key_len 中。
3.9 ref
列出是通過常量(const)么库,還是某個表的某個字段(如果是 join)來過濾(通過 key)的傻丝。
3.10 rows
rows
:以表的統(tǒng)計信息和索引使用情況,估算要找到我們所需的記錄诉儒,需要讀取的行數(shù)葡缰。
這是評估 SQL 性能的一個比較重要的數(shù)據(jù),MySQL 需要掃描的行數(shù),很直觀的顯示 SQL 性能的好壞泛释,一般情況下 rows 值越小越好滤愕。
3.11 filtered
filtered 這個是一個百分比的值,表里符合條件的記錄數(shù)的百分比怜校。簡單點說间影,這個字段表示存儲引擎返回的數(shù)據(jù)在經(jīng)過過濾后,剩下滿足條件的記錄數(shù)量的比例茄茁。
在 MySQL.5.7 版本以前想要顯示 filtered 需要使用 explain extended 命令魂贬。MySQL.5.7 后,默認(rèn) explain 直接顯示 partitions 和 filtered 的信息裙顽。
3.12 Extra
Extra
:不適合在其他列中顯示的信息付燥,Explain 中的很多額外的信息會在 Extra 字段顯示。
3.12.1 Using index
Using index:我們在相應(yīng)的 select 操作中使用了覆蓋索引愈犹,通俗一點講就是查詢的列被索引覆蓋键科,使用到覆蓋索引查詢速度會非常快漩怎,SQL 優(yōu)化中理想的狀態(tài)勋颖。
什么又是覆蓋索引?
一條 SQL 只需要通過索引就可以返回,我們所需要查詢的數(shù)據(jù)(一個或幾個字段)勋锤,而不必通過二級索引饭玲,查到主鍵之后再通過主鍵查詢整行數(shù)據(jù)(SELECT * )。
id 為 user 表的主鍵
mysql> EXPLAIN SELECT id FROM user;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | user | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
注意:想要使用到覆蓋索引叁执,我們在 select 時只取出需要的字段咱枉,不可 SELECT *,而且該字段建了索引徒恋。
mysql> EXPLAIN SELECT * FROM user;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
3.12.2 Using where
Using where:查詢時未找到可用的索引,進而通過 where 條件過濾獲取所需數(shù)據(jù)欢伏,但要注意的是并不是所有帶 where 語句的查詢都會顯示 Using where入挣。
下邊示例 name 并未用到索引,type 為 ALL硝拧,即 MySQL 通過全表掃描后再按 where 條件篩選數(shù)據(jù)径筏。
mysql> EXPLAIN SELECT name FROM user WHERE name = 'riemann';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
3.12.3 Using temporary
Using temporary:表示查詢后結(jié)果需要使用臨時表來存儲,一般在排序或者分組查詢時用到障陶。
mysql> EXPLAIN SELECT name FROM user WHERE id IN (1, 2) GROUP BY name;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------+
| 1 | SIMPLE | user | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where; Using temporary; Using filesort |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------+
3.12.4 Using filesort
Using filesort:表示無法利用索引完成的排序操作滋恬,也就是 ORDER BY 的字段沒有索引,通常這樣的 SQL 都是需要優(yōu)化的抱究。
mysql> EXPLAIN SELECT id FROM user ORDER BY name;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | user | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
如果 ORDER BY 字段有索引就會用到覆蓋索引恢氯,相比執(zhí)行速度快很多。
mysql> EXPLAIN SELECT id FROM user ORDER BY id;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | user | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
3.12.5 Using join buffer
Using join buffer:在我們聯(lián)表查詢的時候,如果表的連接條件沒有用到索引勋拟,需要有一個連接緩沖區(qū)來存儲中間結(jié)果勋磕。
mysql> EXPLAIN SELECT u.name FROM user u, user_test t WHERE u.name = t.name;
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | t | NULL | index | idx_name | idx_name | 83 | NULL | 1 | 100.00 | Using index |
| 1 | SIMPLE | u | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 50.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+----------------------------------------------------+
3.12.6 Impossible where
Impossible where:表示在我們用不太正確的 where 語句,導(dǎo)致沒有符合條件的行敢靡。
mysql> EXPLAIN SELECT name FROM user WHERE 1=2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Impossible WHERE |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
3.12.7 No tables used
No tables used:我們的查詢語句中沒有 FROM 子句挂滓,或者有 FROM DUAL 子句。
mysql> EXPLAIN SELECT now();
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+