執(zhí)行計劃簡介
執(zhí)行計劃是指一條SQL
語句在經(jīng)過MySQL
查詢優(yōu)化器的優(yōu)化會后厢漩,具體的執(zhí)行方式拙寡。MySQL
為我們提供了EXPLAIN
語句举庶,來獲取執(zhí)行計劃的相關(guān)信息。需要注意的是炫狱,EXPLAIN
語句并不會真的去執(zhí)行相關(guān)的語句,而是通過查詢優(yōu)化器對語句進行分析剔猿,找出最優(yōu)的查詢方案视译,并顯示對應(yīng)的信息。
執(zhí)行計劃支持SELECT
, DELETE
, INSERT
, REPLACE
以及 UPDATE
語句归敬。對于SELECT
語句酷含,EXPLAIN
會生成擴展信息,這些信息可以通過緊隨EXPLAIN
語句之后的SHOW WARNINGS
語句顯示汪茧。
執(zhí)行計劃的輸出格式
列 | json名稱 | 含義 |
---|---|---|
id | select_id | 每個SELECT 語句都會對應(yīng)一個唯一的標識符id |
select_type | None |
SELECT 關(guān)鍵字對應(yīng)的查詢類型 |
table | table_name | 每行輸出的表名 |
partitions | partitions | 匹配的分區(qū) |
type | access_type | 表的訪問方法 |
possible_keys | possible_keys | 可能用到的索引 |
key | key | 實際用到的索引 |
key_len | key_length | 實際用到的索引長度 |
ref | ref | 當使用索引等值查詢時椅亚,與索引作比較的列或常量 |
rows | rows | 預(yù)計要讀取的行數(shù) |
filtered | filtered | 按表條件過濾后,留存的記錄數(shù)的百分比(過濾后的行數(shù)/過濾前行數(shù) ) |
extra | None | 附加信息 |
創(chuàng)建測試用表及并生成數(shù)據(jù)
- 創(chuàng)建測試用表
表結(jié)構(gòu)如下所示舱污,實際使用中會創(chuàng)建u1呀舔,u2兩張表,兩張表結(jié)構(gòu)一致扩灯,除id列外媚赖,其它字段隨機插入。
CREATE TABLE u1 (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100),
card_no INT,
school VARCHAR(100),
city VARCHAR(100),
district VARCHAR(100),
street VARCHAR(100),
hoppy VARCHAR(100),
PRIMARY KEY (id),
KEY idx_name (name),
UNIQUE KEY idx_card_no (card_no),
KEY idx_school (school),
KEY idx_address(city, district, street)
) Engine=InnoDB CHARSET=utf8;
- 生成數(shù)據(jù)
先定義一個生成隨機數(shù)的函數(shù)rand_string
:
DELIMITER $$
DROP FUNCTION IF EXISTS rand_string$$
CREATE FUNCTION `rand_string`(num INT) RETURNS varchar(255) CHARSET UTF8
BEGIN
DECLARE origin_str char(52) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str varchar(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < num DO
SET return_str = CONCAT(return_str, SUBSTRING(origin_str , FLOOR(1 + RAND()*52 ),1));
SET i = i +1;
END WHILE;
RETURN return_str;
END $$
DELIMITER ;
創(chuàng)建存儲過程
DELIMITER $$
DROP PROCEDURE IF EXISTS gen_user_data$$
CREATE PROCEDURE `gen_user_data`(num INT)
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= num DO
INSERT INTO u1(id, name, card_no, school, city, district, street, hoppy) VALUES(i, rand_string(1+FLOOR(RAND()*100)), 10000000+i, rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)));
INSERT INTO u2(id, name, card_no, school, city, district, street, hoppy) VALUES(i, rand_string(1+FLOOR(RAND()*100)), 10000000+i, rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)), rand_string(1+FLOOR(RAND()*100)));
SET i = i +1;
END WHILE;
END $$
DELIMITER ;
調(diào)用存儲過程驴剔,生成數(shù)據(jù)
CALL gen_user_data(10000);
執(zhí)行計劃輸出各列詳解
id
查詢語句中每個SELECT
關(guān)鍵字省古,都會有一個對應(yīng)的id
。
最簡單的查詢?nèi)缦滤旧ナВ捎谥挥幸粋€SELECT
關(guān)鍵字豺妓,所以只有id
為1
的記錄。
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
在多表連接的情況下,有可能出現(xiàn)id
相同的情況琳拭。比如下面這種情況训堆,雖然只有一個SELECT
關(guān)鍵字,但是連接查詢了兩張表白嘁,由于每張表都有一行輸出記錄坑鱼,所以一共有兩行記錄;但是由于是同一個SELECT
查詢出來的絮缅,所以id
值相同鲁沥,都是1
。這種情況下耕魄,出現(xiàn)在上面的表叫驅(qū)動表画恰,出現(xiàn)在下面的表叫被驅(qū)動表。下面的示例中u2
是驅(qū)動表吸奴,u1
是被驅(qū)動表允扇。
mysql> EXPLAIN SELECT * FROM u1 inner join u2 WHERE u1.name = u2.name;
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u2 | NULL | ALL | idx_name | NULL | NULL | NULL | 9899 | 100.00 | Using where |
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | zz.u2.name | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
如果一條查詢語句中,出現(xiàn)多個SELECT
關(guān)鍵字则奥,則id
會依次遞增考润。
比如下面這條查詢語句,由于存在子查詢读处,整個查詢一共包含兩個SELECT
關(guān)鍵字糊治,則id
從1
增加到2
。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (select name from u2);
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_key> | <auto_key> | 303 | zz.u1.name | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | u2 | NULL | index | idx_name | idx_name | 303 | NULL | 9899 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
3 rows in set, 1 warning (0.01 sec)
另外档泽,需要注意的是俊戳,子查詢有可能被優(yōu)化為連接查詢,如下面這個查詢馆匿,雖然我們輸入的是子查詢抑胎,但從EXPLAIN
的輸出結(jié)果看,兩行輸出的id
相同渐北,均為1
阿逃,可以看出,子查詢被轉(zhuǎn)化成了連接查詢赃蛛。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (SELECT name FROM u2 WHERE u1.school = u2.school);
+----+-------------+-------+------------+------+---------------------+------------+---------+--------------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------------+------------+---------+--------------+------+----------+-----------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name,idx_school | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | u2 | NULL | ref | idx_name,idx_school | idx_school | 303 | zz.u1.school | 1 | 4.55 | Using where; FirstMatch(u1) |
+----+-------------+-------+------------+------+---------------------+------------+---------+--------------+------+----------+-----------------------------+
2 rows in set, 2 warnings (0.00 sec)
對于包含UNION
子句的查詢來說恃锉,除了會有多個SELECT
關(guān)鍵字外,還會出現(xiàn)一種特殊情況呕臂,就像下面這條語句所展示的破托。
mysql> EXPLAIN SELECT * FROM u1 UNION SELECT * FROM u2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.01 sec)
上面語句的輸出中包含了id
為NULL
的行,且table
列顯示的是<union1,2>
歧蒋,extra
列顯示Using temporary
土砂。<union1,2>
表明州既,MySQL
將id
為1
和id
為2
的兩個查詢結(jié)果合并起來;Using temporary
表明萝映,MySQL
為這個合并結(jié)果創(chuàng)建了一個臨時表吴叶,為什么要創(chuàng)建臨時表呢,主要是為了去重用的序臂。我們知道蚌卤,UNION
查詢的結(jié)果是要去重的。如果不需要去重的話奥秆,還要不要創(chuàng)建臨時表呢逊彭,我們用UNION ALL
語句來試一下:
mysql> EXPLAIN SELECT * FROM u1 UNION ALL SELECT * FROM u2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
可見,如果不需要去重的話吭练,就不會建立臨時表了诫龙。
table
執(zhí)行計劃的輸出結(jié)果中,每行都有對應(yīng)的表名鲫咽,表名除了正常的表之外,也可能是以下列出的值:
-
<unionM,N>
: 本行引用了id
為M
和N
的行的UNION
結(jié)果谷异; -
<derivedN>
: 本行引用了id
為N
的表所產(chǎn)生的的派生表結(jié)果分尸。派生表有可能產(chǎn)生自FROM
語句中的子查詢。 -
<subqueryN>
: 本行引用了id
為N
的表所產(chǎn)生的的物化子查詢結(jié)果歹嘹。
關(guān)于 <unionM,N>
和<subqueryN>
的例子箩绍,我們在講解id
屬性的時候已經(jīng)展示了,下面展示下 <derivedN>
的情況:
mysql> explain SELECT * FROM (SELECT * FROM u1 UNION ALL SELECT * FROM u2) as t;
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 19717 | 100.00 | NULL |
| 2 | DERIVED | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 3 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
3 rows in set, 1 warning (0.00 sec)
這個示例中尺上,table
列為<derived2>
材蛛,說明使用到了id
為2
的派生表。
select_type
根據(jù)每個SELECT
所起到的作用怎抛,對其進行分類卑吭,并為其設(shè)置了select_type
屬性,這個屬性分為以下幾種:
類型 | 英文原文 | 描述 |
---|---|---|
SIMPLE | Simple SELECT (not using UNION or subqueries) |
沒有用到UNION 或子查詢的簡單查詢 |
PRIMARY | Outermost SELECT
|
最外層的SELECT
|
UNION | Second or later SELECT statement in a UNION
|
UNION 中的第二個或更后面的SELECT
|
DEPENDENT UNION | Second or later SELECT statement in a UNION , dependent on outer query |
依賴于外部查詢的马绝,UNION 中的第二個或更后面SELECT
|
UNION RESULT | Result of a UNION
|
UNION 結(jié)果 |
SUBQUERY | First SELECT in subquery |
子查詢中的第一個SELECT
|
DEPENDENT SUBQUERY | First SELECT in subquery, dependent on outer query |
子查詢中的第一個SELECT 豆赏,依賴于外部查詢 |
DERIVED | Derived table | 派生表 |
MATERIALIZED | Materialized subquery | 物化子查詢 |
UNCACHEABLE SUBQUERY | A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query | 無法緩存結(jié)果的子查詢,針對外部查詢的每一行必須重新進行求值 |
UNCACHEABLE UNION | The second or later select in a UNION that belongs to an uncacheable subquery |
UNION 中屬于不可緩存子查詢的第二個或更靠后的SELECT
|
翻譯的不好富稻,大家湊合看哈掷邦,下面我們來詳細講解一下。
-
SIMPLE
如果查詢中不包括UNION
和子查詢椭赋,那么這個SELECT
的類型就是SIMPLE
抚岗。
比如下面所示的單表查詢:
mysql> EXPLAIN SELECT * FROM u1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.03 sec)
當然,連接查詢也屬于SIMPLE
類型:
mysql> EXPLAIN SELECT * FROM u1 INNER JOIN u2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 1 | SIMPLE | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.00 sec)
-
PRIMARY
一條查詢語句中哪怔,可能會包含多個SELECT
關(guān)鍵字宣蔚,最外層的SELECT
向抢,其類型就是PRIMARY
。
mysql> EXPLAIN SELECT * FROM u1 UNION SELECT * FROM u2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
從輸出可以看到件已,整個查詢由兩個SELECT
組成笋额,最外層(最左側(cè))的SELECT
語句為SELECT * FROM u1
,它的select_type
為PRIMARY
篷扩。
UNION
UNION
語句中的第二個或更后的SELECT
關(guān)鍵字兄猩,其類型為UNION
。
比如上面那個UNION
查詢鉴未,最外層的SELECT
其類型為PRIMARY
枢冤,第二個SELECT
查詢SELECT * FROM u2
其類型就是UNION
。DEPENDENT UNION
與UNION
類似铜秆,都是指UNION
語句中的第二個或更后的SELECT
關(guān)鍵字淹真,不過此UNION
查詢依賴于外部查詢。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (SELECT name FROM u1 UNION SELECT name FROM u2);
+----+--------------------+------------+------------+------+---------------+----------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+------------+------------+------+---------------+----------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u1 | NULL | ref | idx_name | idx_name | 303 | func | 1 | 100.00 | Using index |
| 3 | DEPENDENT UNION | u2 | NULL | ref | idx_name | idx_name | 303 | func | 1 | 100.00 | Using index |
| NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------------+------------+------------+------+---------------+----------+---------+------+------+----------+-----------------+
4 rows in set, 1 warning (0.00 sec)
UNION RESULT
MySQL
選擇使用臨時表來完成UNION
查詢的去重工作時连茧,其select_type
就是UNION RESULT
核蘸。上面的示例中就有,就不多說了啸驯。SUBQUERY
如果子查詢沒有被轉(zhuǎn)換成連接查詢客扎,且是不相關(guān)子查詢,且查詢優(yōu)化器對該子查詢做了物化處理罚斗,那么該子查詢的第一個SELECT
語句徙鱼,其select_type
就是SUBQUERY
。
mysql> EXPLAIN SELECT * FROM u1 WHERE name in (SELECT name FROM u2) or name = "abc";
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | SUBQUERY | u2 | NULL | index | idx_name | idx_name | 303 | NULL | 9899 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
:子查詢物化是指創(chuàng)建一張臨時表來保存子查詢結(jié)果针姿,以加快查詢速度和效率袱吆。
MySQL
在首次需要子查詢結(jié)果時創(chuàng)建臨時表,以后再次需要時距淫,則直接從臨時表讀取绞绒,不需要再去重復(fù)執(zhí)行子查詢,提高了查詢效率溉愁。物化時处铛,MySQL
盡可能先在內(nèi)存中創(chuàng)建臨時表,如果表很大拐揭,則會在磁盤上創(chuàng)建臨時表撤蟆。
-
DEPENDENT SUBQUERY
如果子查詢沒有被轉(zhuǎn)換成連接查詢,且是相關(guān)子查詢堂污,那么該子查詢的第一個SELECT
關(guān)鍵字代表的查詢語句家肯,其select_type
就是DEPENDENT SUBQUERY
。
需要注意的是盟猖,對于DEPENDENT SUBQUERY
讨衣,子查詢針對其外部上下文中變量的每個不同值集重新求值一次换棚,也就是說DEPENDENT SUBQUERY
會被執(zhí)行多次。
mysql> EXPLAIN SELECT * FROM u1 WHERE name IN (SELECT name FROM u2 where u2.name = u1.name) OR name="abc";
+----+--------------------+-------+------------+------+---------------+----------+---------+------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+------+---------------+----------+---------+------------+------+----------+--------------------------+
| 1 | PRIMARY | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u2 | NULL | ref | idx_name | idx_name | 303 | zz.u1.name | 1 | 100.00 | Using where; Using index |
+----+--------------------+-------+------------+------+---------------+----------+---------+------------+------+----------+--------------------------+
2 rows in set, 2 warnings (0.01 sec)
-
DERIVED
派生表一般存在于FROM
子句中反镇。
對于采用物化的方式執(zhí)行的包含派生表的查詢固蚤,該派生表對應(yīng)的子查詢的select_type
就是DERIVED
,比方說下邊這個查詢:
mysql> EXPLAIN SELECT * FROM (SELECT name, school FROM u1 GROUP BY name, school) AS t;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 2 | DERIVED | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using temporary; Using filesort |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
2 rows in set, 1 warning (0.00 sec)
又或者下面這條語句:
mysql> EXPLAIN SELECT * FROM (SELECT * FROM u1 UNION ALL SELECT * FROM u2) as t;
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 19717 | 100.00 | NULL |
| 2 | DERIVED | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 3 | UNION | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+-------+----------+-------+
3 rows in set, 1 warning (0.00 sec)
-
MATERIALIZED
當執(zhí)行子查詢語句時歹茶,查詢優(yōu)化器先將子查詢物化后再與外層查詢進行連接查詢時夕玩,其select_type
為MATERIALIZED
:
mysql> EXPLAIN SELECT * FROM u1 WHERE name IN (SELECT name from u2);
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_key> | <auto_key> | 303 | zz.u1.name | 1 | 100.00 | NULL |
| 2 | MATERIALIZED | u2 | NULL | index | idx_name | idx_name | 303 | NULL | 9899 | 100.00 | Using index |
+----+--------------+-------------+------------+--------+---------------+------------+---------+------------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
從執(zhí)行計劃可以看出,有兩行id
為1
的記錄惊豺,說明優(yōu)化器將子查詢轉(zhuǎn)換成了連接查詢燎孟,其id
為2
的行的select_type
為MATERIALIZED
,說明是先將子查詢進行物化尸昧,然后再轉(zhuǎn)換成了連接查詢揩页。
UNCACHEABLE SUBQUERY
略過。UNCACHEABLE UNION
略過烹俗。
partitions
查詢記錄中匹配的分區(qū)爆侣。對于非分區(qū)表,該值為NULL幢妄。
type
EXPLAIN
執(zhí)行計劃中的每一行都代表著對一張表的查詢累提,而type
列則表示該表的訪問方法。訪問方法按從好到壞磁浇,依次為system
、const
朽褪,eq_ref
置吓,ref
,fulltext
缔赠,ref_or_null
衍锚,index_merge
,unique_subquery
嗤堰,index_subquery
戴质,range
,index
踢匣,ALL
告匠,下面分別來介紹一下。
-
system
如果表使用的引擎對于表行數(shù)統(tǒng)計是精確的(如:MyISAM)离唬,且表中只有一行記錄的情況下后专,訪問方法是system
。
先看看存儲引擎為InnoDB
的表訪問方法:
mysql> CREATE TABLE single_row (id INT NOT NULL) engine=InnoDB;
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO single_row VALUES(1);
Query OK, 1 row affected (0.01 sec)
mysql> EXPLAIN SELECT * FROM single_row;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | single_row | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
可以看到输莺,對于InnoDB
引擎戚哎,type
類型為ALL
裸诽,換成MyISAM
引擎看看:
mysql> ALTER TABLE single_row engine=MyISAM;
Query OK, 1 row affected (0.03 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> EXPLAIN SELECT * FROM single_row;
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | single_row | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
可以看到,改成MyISAM
引擎后型凳,type
類型為system
丈冬。
-
const
當使用主鍵或唯一索引與常量作等值比較時(如果主鍵或唯一索引是聯(lián)合索引的話,則聯(lián)合索引的每一部分都必須是和常量做等值比較)甘畅,就會使用const
埂蕊,例如:
mysql> EXPLAIN SELECT * FROM u1 WHERE id=1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
-
eq_ref
對于連接查詢來說,如果被驅(qū)動表是使用主鍵或者非空唯一索引與常量進行等值比較(如果主鍵或非空唯一索引是聯(lián)合索引橄浓,則聯(lián)合索引的每一列都要做等值比較)的方法進行訪問的粒梦,那么該驅(qū)動表訪問方法為eq_ref
。
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.id=u2.id;
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 1 | SIMPLE | u2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | zz.u1.id | 1 | 100.00 | NULL |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec)
需要注意的是荸实,如果唯一索引允許為NULL
匀们,那么訪問方法就不是eq_ref
,請看如下示例:
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.card_no=u2.card_no;
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_card_no | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 1 | SIMPLE | u2 | NULL | ref | idx_card_no | idx_card_no | 5 | zz.u1.card_no | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
可以看到准给,在唯一索引允許為NULL
時泄朴,訪問方法為ref
。
-
ref
當通過普通二級索引列與常量進行等值匹配來查詢時露氮,其訪問方法有可能是ref
祖灰,ref
可用于使用=
或<=>
運算符進行比較的索引列。 在以下示例中畔规,MySQL
可以使用ref
訪問方法來處理查詢:
mysql> EXPLAIN SELECT * FROM u1 WHERE name="abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM u1 WHERE name <=> "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
fulltext
連接使用了fulltext
索引辜窑。ref_or_null
這種連接類型類似于ref
,但是MySQL
會額外搜索包含NULL
值的行娩践。 在以下示例中陪腌,MySQL可以使用ref_or_null
的訪問方法來查詢:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" OR name IS NULL;
+----+-------------+-------+------------+-------------+---------------+----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------+----------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | ref_or_null | idx_name | idx_name | 303 | const | 2 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------------+---------------+----------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-
index_merge
查詢優(yōu)化器選擇使用索引合并來優(yōu)化查詢。索引合并分為Intersection
莫绣、Union
畴蒲、Sort-Union
三種。在使用索引合并的情況下对室,輸出行中的key
列包含使用的索引列表模燥,而key_len
包含使用的索引的長度(按長度最長的情況計算)列表。
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" OR school = "abc";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 2 | 100.00 | Using union(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
-
unique_subquery
類似于兩表連接時掩宜,被驅(qū)動表的eq_ref
方法蔫骂;unique_subquery
被用于IN
子查詢中,如果IN
子查詢中能夠用主鍵索引做等值匹配進行查詢的話锭亏,那么該子查詢的訪問方法為unique_subquery
纠吴。
mysql> EXPLAIN SELECT * FROM u1 WHERE id IN (SELECT id FROM u2 WHERE u1.card_no = u2.card_no) OR card_no = 1000;
+----+--------------------+-------+------------+-----------------+---------------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+-----------------+---------------------+---------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u1 | NULL | ALL | idx_card_no | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u2 | NULL | unique_subquery | PRIMARY,idx_card_no | PRIMARY | 4 | func | 1 | 10.00 | Using where |
+----+--------------------+-------+------------+-----------------+---------------------+---------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)
可以看到,對u2
表的查詢使用的是PRIMARY
主鍵索引慧瘤,其type
列為unique_subquery
戴已。
-
index_subquery
類似于unique_subquery
固该,只不過在子查詢中用到的是非唯一索引:
mysql> EXPLAIN SELECT * FROM u1 WHERE school IN (SELECT school FROM u2 WHERE u1.name = u2.name) OR hoppy = "abc";
+----+--------------------+-------+------------+----------------+---------------------+------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-------+------------+----------------+---------------------+------------+---------+------+------+----------+-------------+
| 1 | PRIMARY | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using where |
| 2 | DEPENDENT SUBQUERY | u2 | NULL | index_subquery | idx_name,idx_school | idx_school | 303 | func | 1 | 10.00 | Using where |
+----+--------------------+-------+------------+----------------+---------------------+------------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)
-
range
當使用索引進行范圍查詢時,其訪問類型為range
糖儡。
mysql> EXPLAIN SELECT * FROM u1 WHERE id > 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 4909 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
mysql> EXPLAIN SELECT * FROM u1 WHERE name LIKE "abc%";
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | range | idx_name | idx_name | 303 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-
index
如果查詢中用的是覆蓋索引伐坏,索引中的數(shù)據(jù)就可滿足查詢需要,此時的訪問方法為index
握联。
mysql> EXPLAIN SELECT street FROM u1;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | index | NULL | idx_address | 909 | NULL | 9818 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-
ALL
當使用全表掃描的方式進行查詢時桦沉,訪問方法為ALL
。 通常金闽,可以通過添加索引來避免ALL
訪問方法纯露。
mysql> EXPLAIN SELECT * FROM u1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
possible_keys
possible_keys
列表示MySQL
執(zhí)行查詢時可能用到的索引。如果這一列為NULL
代芜,則沒有使用到索引埠褪;這種情況下,需要檢查WHERE
語句中所使用的的列挤庇,看是否可以通過給這些列中某個或多個添加索引的方法來提高查詢性能钞速。
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" AND school = "abc";
+----+-------------+-------+------------+------+---------------------+----------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------------+----------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name,idx_school | idx_name | 303 | const | 1 | 5.00 | Using where |
+----+-------------+-------+------------+------+---------------------+----------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
可以看到,possible_keys
列顯示了兩種可用的索引idx_name
和idx_school
嫡秕,從key
列可以看到渴语,實際使用的是idx_name
索引。
需要注意的一點是昆咽,當type
為index
時驾凶,會出現(xiàn)possible_keys
為NULL
而key
列為實際使用到的索引的情況,如下所示:
mysql> EXPLAIN SELECT city FROM u1;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | index | NULL | idx_address | 909 | NULL | 9818 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
key
key
列表示MySQL
實際使用到的索引掷酗。
當使用覆蓋索引時狭郑,key
有可能會命中possible_keys
中不存在的索引。key_len
key_len
列表示MySQL
實際使用的索引的最大長度汇在;當使用到聯(lián)合索引時,有可能是多個列的長度和脏答。
索引長度由三個部分組成:
1糕殉、索引本身字節(jié)數(shù)
對于固定長度類型的索引列來說,就是它實際占用的存儲空間的長度殖告,比如說int
類型占用4
個字節(jié)阿蝶;對于可變長度類型來說,是它實際占用的存儲空間的最大長度黄绩,比如說varchar(100)
類型羡洁,如果編碼類型為utf8
,那么該長度為100*3=300
個字節(jié)爽丹,如果編碼類型為utf8mb4
筑煮,那么該長度為100*4=400
個字節(jié)
2辛蚊、如果該索引列允許為NULL
值,則key_len
再加1
個字節(jié)真仲。
3袋马、如果是變長字段,則key_len
再加2
個字節(jié)秸应。
示例如下:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
由于name
列為varchar(100)
類型虑凛,編碼格式為utf8
并且允許為NULL
,則計算公式如下:
100 * 3 + 1 + 2 = 303
再來看一下固定長度索引的示例:
mysql> EXPLAIN SELECT * FROM u1 WHERE id = 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
上例中使用到了主鍵索引软啼,int
類型且不允許為NULL
桑谍,索引key_len
列顯示為4
。
- ref
ref
列顯示的是祸挪,通過索引列做等值比較查詢時(也就是在訪問方法是const
锣披、eq_ref
、ref
匕积、ref_or_null
盈罐、unique_subquery
、index_subquery
其中之一時)闪唆,哪些列或常量被用來與key
列中的索引做比較盅粪。
示例如下:
mysql> EXPLAIN SELECT * FROM u1 WHERE id = 1000;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | u1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.name = u2.name;
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u2 | NULL | ALL | idx_name | NULL | NULL | NULL | 9899 | 100.00 | Using where |
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | zz.u2.name | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
如果顯示為func
, 則表示使用到的值是某個函數(shù)的結(jié)果悄蕾。
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.name = SUBSTRING(u2.name, 1, 10);
+----+-------------+-------+------------+------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 100.00 | NULL |
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | func | 1 | 100.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+----------+---------+------+------+----------+-----------------------+
2 rows in set, 1 warning (0.00 sec)
rows
rows
列表示MySQL
認為執(zhí)行查詢時所需要檢查的行數(shù)票顾。對于InnoDB
表,這個值為估計值帆调,并不完全準確奠骄。filtered
filtered
列表示被查詢條件過濾后的留存百分比。最大值為100番刊,意味著實際并沒有過濾掉任何行含鳞。此值從100逐漸降低時,表示被過濾掉的行數(shù)越來越多芹务。
rows
列表示MySQL
要檢查的行數(shù)的估計值蝉绷,rows × filtered
表示過濾后,要和下張表進行join
的行數(shù)枣抱。
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.name = u2.name AND u1.hoppy = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | idx_name | NULL | NULL | NULL | 9818 | 10.00 | Using where |
| 1 | SIMPLE | u2 | NULL | ref | idx_name | idx_name | 303 | zz.u1.name | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
從執(zhí)行計劃可以看出熔吗,u1
為驅(qū)動表,u2
為被驅(qū)動表佳晶。驅(qū)動表預(yù)計掃描9818
行記錄桅狠,有10%
的記錄會被保留下來,也就是說驅(qū)動表大概有9818 * 10% = 981.8
條記錄被用來u2
表做連接,也就是說被驅(qū)動表將會被執(zhí)行約982
次中跌。
Extra
這列包含了MySQL
解析查詢的額外信息咨堤,通過這些信息,可以更準確的理解MySQL
到底是如何執(zhí)行查詢的晒他。
Extra
列信息比較多吱型,我們挑一些常用的來講解一下。
-
Using index
在使用了覆蓋索引時陨仅,Extra
列會顯示Using index
信息津滞。
如下所示:
mysql> EXPLAIN SELECT city, street FROM u1;
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | index | NULL | idx_address | 909 | NULL | 9818 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-
Using index condition
索引條件下推的詳細內(nèi)容我在單獨的一篇文章里介紹過了,不熟悉的小伙伴可以看這里:一文讀懂什么是MySQL索引下推(ICP)
如果查詢優(yōu)化器選擇使用索引條件下推這個特性灼伤,在Extra列中將會顯示Using index condition
触徐,比如下面的查詢:
mysql> EXPLAIN SELECT * FROM u1 WHERE city = "abc" AND district LIKE "%abc";
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_address | idx_address | 303 | const | 1 | 11.11 | Using index condition |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-
Using where
如果查詢沒有用到索引(也就是全表掃描),那么當查詢語句的WHERE
條件中有針對該表的列搜索時狐赡,Extra
列會顯示Using where
撞鹉。請看下面的示例:
mysql> EXPLAIN SELECT * FROM u1 WHERE hoppy = 'abc';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
如果查詢使用到了索引,那么當WHERE
條件中包含該索引之外的列時颖侄,Extra
列會顯示Using where
鸟雏。請看下面的示例:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "abc" AND hoppy = "abc";
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | u1 | NULL | ref | idx_name | idx_name | 303 | const | 1 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-
Using join buffer (Block Nested Loop)
在連接查詢執(zhí)行過程中,如果被驅(qū)動表不能有效的利用索引加快查詢速度览祖,MySQL
一般會為其分配一塊名叫join buffer
的內(nèi)存塊來加快查詢速度孝鹊,也就是基于塊的嵌套循環(huán)算法
,比如下面這個查詢:
mysql> EXPLAIN SELECT * FROM u1, u2 WHERE u1.hoppy = u2.hoppy;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | NULL |
| 1 | SIMPLE | u2 | NULL | ALL | NULL | NULL | NULL | NULL | 9899 | 10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
-
Using sort_union(...), Using union(...), Using intersect(...)
如果執(zhí)行計劃的Extra
列出現(xiàn)了Using intersect(...)
提示展蒂,說明準備使用Intersect
索引合并的方式執(zhí)行查詢又活,括號中的...
表示需要進行索引合并的索引名稱;如果出現(xiàn)了Using union(...)
提示锰悼,說明準備使用Union
索引合并的方式執(zhí)行查詢柳骄;出現(xiàn)了Using sort_union(...)
提示,說明準備使用Sort-Union
索引合并的方式執(zhí)行查詢箕般。
比如這個查詢的執(zhí)行計劃耐薯,使用到了Intersect
索引合并:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "a" AND school = "b";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 1 | 100.00 | Using intersect(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+---------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
再比如下面這個查詢的執(zhí)行計劃,使用到了Union
索引合并:
mysql> EXPLAIN SELECT * FROM u1 WHERE name = "a" OR school = "b";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 8 | 100.00 | Using union(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+-----------------------------------------------+
1 row in set, 1 warning (0.00 sec)
下面這個查詢語句丝里,使用到了Sort-Union
索引合并:
mysql> EXPLAIN SELECT * FROM u1 WHERE name < "a" OR school < "b";
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | u1 | NULL | index_merge | idx_name,idx_school | idx_name,idx_school | 303,303 | NULL | 395 | 100.00 | Using sort_union(idx_name,idx_school); Using where |
+----+-------------+-------+------------+-------------+---------------------+---------------------+---------+------+------+----------+----------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
-
Using filesort
有一些情況下對結(jié)果集中的記錄進行排序時可以使用到索引可柿,但是在很多情況下,無法使用到索引丙者,只能在內(nèi)存(數(shù)據(jù)較少)或磁盤(數(shù)據(jù)較大)中進行排序。這種在內(nèi)存中或者磁盤上進行排序的方式統(tǒng)稱為文件排序(英文名:filesort
)营密。如果某個查詢需要使用文件排序的方式執(zhí)行查詢械媒,就會在執(zhí)行計劃的Extra
列中顯示Using filesort
提示,比如下面這條語句:
mysql> EXPLAIN SELECT * FROM u1 ORDER BY hoppy;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
:通常來講,出現(xiàn)文件排序會使查詢性能變差纷捞,應(yīng)該
盡量避免
痢虹,盡量將文件排序
提升為索引排序
。
-
Using temporary
在許多查詢的執(zhí)行過程中主儡,MySQL
可能會借助臨時表來完成一些功能奖唯,比如去重、排序之類的糜值。比如我們在執(zhí)行許多包含DISTINCT
丰捷、GROUP BY
、UNION
等子句的查詢過程中寂汇,如果不能有效利用索引來完成查詢病往,MySQL
很有可能尋求通過建立內(nèi)部的臨時表來執(zhí)行查詢。如果查詢中使用到了內(nèi)部的臨時表骄瓣,在執(zhí)行計劃的Extra
列將會顯示Using temporary
提示停巷,比如下面的語句:
mysql> EXPLAIN SELECT hoppy, count(*) as cnt FROM u1 GROUP BY hoppy;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using temporary; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec)
可以看到,上述執(zhí)行計劃的Extra
列不僅僅包含Using temporary
提示榕栏,還包含Using filesort
提示畔勤,可是我們的查詢語句中明明沒有寫ORDER BY
子句呀?這是因為MySQL
會在包含GROUP BY
子句的查詢中默認添加上ORDER BY
子句扒磁。如果我們并不想為包含GROUP BY
子句的查詢進行排序庆揪,需要我們顯式的寫上ORDER BY NULL
,就像這樣:
mysql> EXPLAIN SELECT hoppy, count(*) as cnt FROM u1 GROUP BY hoppy ORDER BY NULL;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| 1 | SIMPLE | u1 | NULL | ALL | NULL | NULL | NULL | NULL | 9818 | 100.00 | Using temporary |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------+
1 row in set, 1 warning (0.00 sec)
可見渗磅,Extra
列里沒有出現(xiàn)Using filesort
提示了嚷硫,這就節(jié)約了排序成本,提升了查詢效率始鱼。
:與
Using filesort
類似仔掸,Extra
列出現(xiàn)Using temporary
也不是什么好事,應(yīng)該盡量避免医清。