MySQL关键字EXPLAIN的用法及其案例
文章目錄
- 概述
- EXPLAIN輸出的列的解釋
- 實(shí)例說明
- select_type的說明
- UNION
- DEPENDENT UNION與DEPENDENT SUBQUERY
- SUBQUERY
- DERIVED
- type的說明
- system,const
- eq_ref
- ref
- ref_or_null
- index_merge
- unique_subquery
- index_subquery
- range
- index
- ALL
- extra的說明
- Distinct
- Not exists
- Range checked for each record
- Using filesort
- Using index
- Using temporary
- Using where
- Using sort_union(...)/Using union(...)/Using intersect(...)
- Using index for group-by
- 一個(gè)SQL語(yǔ)句的優(yōu)化例子
- 參考資料
概述
根據(jù)表、列、索引和WHERE子句中的條件的詳細(xì)信息,MySQL優(yōu)化器考慮了許多技術(shù)來高效地執(zhí)行SQL查詢。
- 可以在不讀取所有行的情況下對(duì)大型表執(zhí)行查詢;
- 可以在不比較每一行組合的情況下執(zhí)行包含多個(gè)表的聯(lián)接。
優(yōu)化器選擇執(zhí)行最有效查詢的一組操作稱為“查詢執(zhí)行計(jì)劃(query execution plan)”,也稱為解釋計(jì)劃(EXPLAIN plan)。
我們目標(biāo)是認(rèn)識(shí)到EXPLAIN計(jì)劃中表示查詢優(yōu)化良好的方面,并學(xué)習(xí)SQL語(yǔ)法和索引技術(shù),以便在看到一些低效的操作時(shí)改進(jìn)該計(jì)劃。
使用方法,就是在SELECT語(yǔ)句前加上EXPLAIN即可:
EXPLAIN SELECT * FROM question;輸出結(jié)果如下:
mysql> explain SELECT * FROM question; +----+-------------+----------+------+---------------+------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+------+---------------+------+---------+------+------+-------+ | 1 | SIMPLE | question | ALL | NULL | NULL | NULL | NULL | 383 | | +----+-------------+----------+------+---------------+------+---------+------+------+-------+ 1 row in setEXPLAIN 也可以用在DELETE, INSERT, REPLACE, 和 UPDATE語(yǔ)句上。
EXPLAIN輸出的列的解釋
- id:SELECT識(shí)別符。這是SELECT的查詢序列號(hào)
- select_type:SELECT類型,可以為以下任何一種:
- SIMPLE:簡(jiǎn)單SELECT(不使用UNION或子查詢)
- PRIMARY:最外面的SELECT
- UNION:UNION中的第二個(gè)或后面的SELECT語(yǔ)句
- DEPENDENT UNION:UNION中的第二個(gè)或后面的SELECT語(yǔ)句,取決于外面的查詢
- UNION RESULT:UNION 的結(jié)果
- SUBQUERY:子查詢中的第一個(gè)SELECT
- DEPENDENT SUBQUERY:子查詢中的第一個(gè)SELECT,取決于外面的查詢
- DERIVED:導(dǎo)出表的SELECT(FROM子句的子查詢)
- table:輸出的行所引用的表
- type:聯(lián)接類型。下面給出各種聯(lián)接類型,按照從最佳類型到最壞類型進(jìn)行排序:
- system:表僅有一行(=系統(tǒng)表)。這是const聯(lián)接類型的一個(gè)特例。
- const:表最多有一個(gè)匹配行,它將在查詢開始時(shí)被讀取。因?yàn)閮H有一行,在這行的列值可被優(yōu)化器剩余部分認(rèn)為是常數(shù)。const表很快,因?yàn)樗鼈冎蛔x取一次
- eq_ref:對(duì)于每個(gè)來自于前面的表的行組合,從該表中讀取一行。這可能是最好的聯(lián)接類型,除了const類型。
- ref:對(duì)于每個(gè)來自于前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。
- ref_or_null:該聯(lián)接類型如同ref,但是添加了MySQL可以專門搜索包含NULL值的行。
- index_merge:該聯(lián)接類型表示使用了索引合并優(yōu)化方法。
- unique_subquery:該類型替換了下面形式的IN子查詢的ref: value IN (SELECT primary_key FROM single_table WHERE some_expr) unique_subquery是一個(gè)索引查找函數(shù),可以完全替換子查詢,效率更高。
- index_subquery:該聯(lián)接類型類似于unique_subquery。可以替換IN子查詢,但只適合下列形式的子查詢中的非唯一索引: value IN (SELECT key_column FROM single_table WHERE some_expr)
- range:只檢索給定范圍的行,使用一個(gè)索引來選擇行。
- index:該聯(lián)接類型與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因?yàn)樗饕募ǔ1葦?shù)據(jù)文件小。
- ALL:對(duì)于每個(gè)來自于先前的表的行組合,進(jìn)行完整的表掃描。
- possible_keys:指出MySQL能使用哪個(gè)索引在該表中找到行
- key:顯示MySQL實(shí)際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。
- key_len:顯示MySQL決定使用的鍵長(zhǎng)度。如果鍵是NULL,則長(zhǎng)度為NULL。
- ref:顯示使用哪個(gè)列或常數(shù)與key一起從表中選擇行。
- rows:顯示MySQL認(rèn)為它執(zhí)行查詢時(shí)必須檢查的行數(shù)。多行之間的數(shù)據(jù)相乘可以估算要處理的行數(shù)。
- filtered:顯示了通過條件過濾出的行數(shù)的百分比估計(jì)值。
- Extra:該列包含MySQL解決查詢的詳細(xì)信息
- Distinct:MySQL發(fā)現(xiàn)第1個(gè)匹配行后,停止為當(dāng)前的行組合搜索更多的行。
- Not exists:MySQL能夠?qū)Σ樵冞M(jìn)行LEFT JOIN優(yōu)化,發(fā)現(xiàn)1個(gè)匹配LEFT JOIN標(biāo)準(zhǔn)的行后,不再為前面的的行組合在該表內(nèi)檢查更多的行。
- range checked for each record (index map: #):MySQL沒有發(fā)現(xiàn)好的可以使用的索引,但發(fā)現(xiàn)如果來自前面的表的列值已知,可能部分索引可以使用。
- Using filesort:MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。
- Using index:從只使用索引樹中的信息而不需要進(jìn)一步搜索讀取實(shí)際的行來檢索表中的列信息。
- Using temporary:為了解決查詢,MySQL需要?jiǎng)?chuàng)建一個(gè)臨時(shí)表來容納結(jié)果。
- Using where:WHERE 子句用于限制哪一個(gè)行匹配下一個(gè)表或發(fā)送到客戶。
- Using sort_union(…), Using union(…), Using intersect(…):這些函數(shù)說明如何為index_merge聯(lián)接類型合并索引掃描。
- Using index for group-by:類似于訪問表的Using index方式,Using index for group-by表示MySQL發(fā)現(xiàn)了一個(gè)索引,可以用來查 詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜索硬盤訪問實(shí)際的表。
實(shí)例說明
select_type的說明
UNION
當(dāng)通過union來連接多個(gè)查詢結(jié)果時(shí),第二個(gè)之后的select其select_type為UNION。
mysql> explain select * from t_order where order_id=100 union select * from t_order where order_id=200; +----+--------------+------------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------+------------+-------+---------------+---------+---------+-------+------+-------+ | 1 | PRIMARY | t_order | const | PRIMARY | PRIMARY | 4 | const | 1 | | | 2 | UNION | t_order | const | PRIMARY | PRIMARY | 4 | const | 1 | | | NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | | +----+--------------+------------+-------+---------------+---------+---------+-------+------+-------+ 3 rows in set (0.34 sec)DEPENDENT UNION與DEPENDENT SUBQUERY
當(dāng)union在子查詢中作用時(shí),其中第二個(gè)union的select_type就是DEPENDENT UNION。
第一個(gè)子查詢的select_type則是DEPENDENT SUBQUERY。
dependent
英 [d??pend?nt] 美 [d??pend?nt]
adj. 依靠的;依賴的;有癮的;受…的影響;取決于
n.被扶養(yǎng)人:被贍養(yǎng)人:非獨(dú)立生活的人;依靠者;從屬物
SUBQUERY
子查詢中的第一個(gè)select其select_type為SUBQUERY。
mysql> explain select * from t_order where order_id=(select order_id from t_order where order_id=100); +----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ | 1 | PRIMARY | t_order | const | PRIMARY | PRIMARY | 4 | const | 1 | | | 2 | SUBQUERY | t_order | const | PRIMARY | PRIMARY | 4 | | 1 | Using index | +----+-------------+---------+-------+---------------+---------+---------+-------+------+-------------+ 2 rows in set (0.03 sec)DERIVED
當(dāng)子查詢是from子句時(shí),其select_type為DERIVED。
mysql> explain select * from (select order_id from t_order where order_id=100) a; +----+-------------+------------+--------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+---------------+---------+---------+------+------+-------------+ | 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | | | 2 | DERIVED | t_order | const | PRIMARY | PRIMARY | 4 | | 1 | Using index | +----+-------------+------------+--------+---------------+---------+---------+------+------+-------------+ 2 rows in set (0.03 sec)type的說明
system,const
DERIVED的示例中,其中第一行的type就是為system,第二行是const,這兩種聯(lián)接類型是最快的。
eq_ref
在t_order表中的order_id是主鍵,t_order_ext表中的order_id也是主鍵,該表可以認(rèn)為是訂單表的補(bǔ)充信息表,他們的關(guān)系是1對(duì)1,在下面的例子中可以看到b表的連接類型是eq_ref,這是極快的聯(lián)接類型。
mysql> explain select * from t_order a,t_order_ext b where a.order_id=b.order_id; +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+ | 1 | SIMPLE | b | ALL | order_id | NULL | NULL | NULL | 1 | | | 1 | SIMPLE | a | eq_ref | PRIMARY | PRIMARY | 4 | test.b.order_id | 1 | Using where | +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+ 2 rows in set (0.00 sec)ref
下面的例子在上面的例子上略作了修改,加上了條件。此時(shí)b表的聯(lián)接類型變成了ref。因?yàn)樗信ca表中order_id=100的匹配記錄都將會(huì)從b表獲取。這是比較常見的聯(lián)接類型。
mysql> explain select * from t_order a,t_order_ext b where a.order_id=b.order_id and a.order_id=100; +----+-------------+-------+-------+---------------+----------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+----------+---------+-------+------+-------+ | 1 | SIMPLE | a | const | PRIMARY | PRIMARY | 4 | const | 1 | | | 1 | SIMPLE | b | ref | order_id | order_id | 4 | const | 1 | | +----+-------------+-------+-------+---------------+----------+---------+-------+------+-------+ 2 rows in set (0.00 sec)ref_or_null
user_id字段是一個(gè)可以為空的字段,并對(duì)該字段創(chuàng)建了一個(gè)索引。在下面的查詢中可以看到聯(lián)接類型為ref_or_null,這是mysql為含有null的字段專門做的處理。在我們的表設(shè)計(jì)中應(yīng)當(dāng)盡量避免索引字段為NULL,因?yàn)檫@會(huì)額外的耗費(fèi)mysql的處理時(shí)間來做優(yōu)化。
mysql> explain select * from t_order where user_id=100 or user_id is null; +----+-------------+---------+-------------+---------------+---------+---------+-------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------------+---------------+---------+---------+-------+-------+-------------+ | 1 | SIMPLE | t_order | ref_or_null | user_id | user_id | 5 | const | 50325 | Using where | +----+-------------+---------+-------------+---------------+---------+---------+-------+-------+-------------+ 1 row in set (0.00 sec)index_merge
經(jīng)常出現(xiàn)在使用一張表中的多個(gè)索引時(shí)。mysql會(huì)將多個(gè)索引合并在一起,如下例:
mysql> explain select * from t_order where order_id=100 or user_id=10; +----+-------------+---------+-------------+-----------------+-----------------+---------+------+------+-------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------------+-----------------+-----------------+---------+------+------+-------------------------------------------+ | 1 | SIMPLE | t_order | index_merge | PRIMARY,user_id | PRIMARY,user_id | 4,5 | NULL | 2 | Using union(PRIMARY,user_id); Using where | +----+-------------+---------+-------------+-----------------+-----------------+---------+------+------+-------------------------------------------+ 1 row in set (0.09 sec)unique_subquery
該聯(lián)接類型用于替換value IN (SELECT primary_key FROM single_table WHERE some_expr)這樣的子查詢的ref。注意ref列,其中第二行顯示的是func,表明unique_subquery是一個(gè)函數(shù),而不是一個(gè)普通的ref。
mysql> explain select * from t_order where order_id in (select order_id from t_order where user_id=10); +----+--------------------+---------+-----------------+-----------------+---------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+---------+-----------------+-----------------+---------+---------+------+--------+-------------+ | 1 | PRIMARY | t_order | ALL | NULL | NULL | NULL | NULL | 100649 | Using where | | 2 | DEPENDENT SUBQUERY | t_order | unique_subquery | PRIMARY,user_id | PRIMARY | 4 | func | 1 | Using where | +----+--------------------+---------+-----------------+-----------------+---------+---------+------+--------+-------------+ 2 rows in set (0.00 sec)index_subquery
該聯(lián)接類型與上面的太像了,唯一的差別就是子查詢查的不是主鍵而是非唯一索引。
mysql> explain select * from t_order where user_id in (select user_id from t_order where order_id>10); +----+--------------------+---------+----------------+-----------------+---------+---------+------+--------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+---------+----------------+-----------------+---------+---------+------+--------+--------------------------+ | 1 | PRIMARY | t_order | ALL | NULL | NULL | NULL | NULL | 100649 | Using where | | 2 | DEPENDENT SUBQUERY | t_order | index_subquery | PRIMARY,user_id | user_id | 5 | func | 50324 | Using index; Using where | +----+--------------------+---------+----------------+-----------------+---------+---------+------+--------+--------------------------+ 2 rows in set (0.00 sec)range
按指定的范圍進(jìn)行檢索,很常見。
mysql> explain select * from t_order where user_id in (100,200,300); +----+-------------+---------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | t_order | range | user_id | user_id | 5 | NULL | 3 | Using where | +----+-------------+---------+-------+---------------+---------+---------+------+------+-------------+ 1 row in set (0.00 sec)index
在進(jìn)行統(tǒng)計(jì)時(shí)非常常見,此聯(lián)接類型實(shí)際上會(huì)掃描索引樹,僅比ALL快些。
mysql> explain select count(*) from t_order; +----+-------------+---------+-------+---------------+---------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+---------+---------+------+--------+-------------+ | 1 | SIMPLE | t_order | index | NULL | user_id | 5 | NULL | 100649 | Using index | +----+-------------+---------+-------+---------------+---------+---------+------+--------+-------------+ 1 row in set (0.00 sec)ALL
完整的掃描全表,最慢的聯(lián)接類型,盡可能地避免。mysql> explain select * from t_order; +----+-------------+---------+------+---------------+------+---------+------+--------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+------+---------+------+--------+-------+ | 1 | SIMPLE | t_order | ALL | NULL | NULL | NULL | NULL | 100649 | | +----+-------------+---------+------+---------------+------+---------+------+--------+-------+ 1 row in set (0.00 sec)extra的說明
Distinct
MySQL發(fā)現(xiàn)第1個(gè)匹配行后,停止為當(dāng)前的行組合搜索更多的行。對(duì)于此項(xiàng)沒有找到合適的例子,求指點(diǎn)。
Not exists
因?yàn)閎表中的order_id是主鍵,不可能為NULL,所以mysql在用a表的order_id掃描t_order表,并查找b表的行時(shí),如果在b表發(fā)現(xiàn)一個(gè)匹配的行就不再繼續(xù)掃描b了,因?yàn)閎表中的order_id字段不可能為NULL。這樣避免了對(duì)b表的多次掃描。
mysql> explain select count(1) from t_order a left join t_order_ext b on a.order_id=b.order_id where b.order_id is null; +----+-------------+-------+-------+---------------+--------------+---------+-----------------+--------+--------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+--------------+---------+-----------------+--------+--------------------------------------+ | 1 | SIMPLE | a | index | NULL | express_type | 1 | NULL | 100395 | Using index | | 1 | SIMPLE | b | ref | order_id | order_id | 4 | test.a.order_id | 1 | Using where; Using index; Not exists | +----+-------------+-------+-------+---------------+--------------+---------+-----------------+--------+--------------------------------------+ 2 rows in set (0.01 sec)Range checked for each record
這種情況是mysql沒有發(fā)現(xiàn)好的索引可用,速度比沒有索引要快得多。
mysql> explain select * from t_order t, t_order_ext s where s.order_id>=t.order_id and s.order_id<=t.order_id and t.express_type>5; +----+-------------+-------+-------+----------------------+--------------+---------+------+------+------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+----------------------+--------------+---------+------+------+------------------------------------------------+ | 1 | SIMPLE | t | range | PRIMARY,express_type | express_type | 1 | NULL | 1 | Using where | | 1 | SIMPLE | s | ALL | order_id | NULL | NULL | NULL | 1 | Range checked for each record (index map: 0x1) | +----+-------------+-------+-------+----------------------+--------------+---------+------+------+------------------------------------------------+ 2 rows in set (0.00 sec)Using filesort
在有排序子句的情況下很常見的一種情況。此時(shí)mysql會(huì)根據(jù)聯(lián)接類型瀏覽所有符合條件的記錄,并保存排序關(guān)鍵字和行指針,然后排序關(guān)鍵字并按順序檢索行。
mysql> explain select * from t_order order by express_type; +----+-------------+---------+------+---------------+------+---------+------+--------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+------+---------+------+--------+----------------+ | 1 | SIMPLE | t_order | ALL | NULL | NULL | NULL | NULL | 100395 | Using filesort | +----+-------------+---------+------+---------------+------+---------+------+--------+----------------+ 1 row in set (0.00 sec)Using index
這是性能很高的一種情況。當(dāng)查詢所需的數(shù)據(jù)可以直接從索引樹中檢索到時(shí),就會(huì)出現(xiàn)。上面的例子中有很多這樣的例子,不再多舉例了。
Using temporary
發(fā)生這種情況一般都是需要進(jìn)行優(yōu)化的。mysql需要?jiǎng)?chuàng)建一張臨時(shí)表用來處理此類查詢。
mysql> explain select * from t_order a left join t_order_ext b on a.order_id=b.order_id group by b.order_id; +----+-------------+-------+------+---------------+----------+---------+-----------------+--------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+----------+---------+-----------------+--------+---------------------------------+ | 1 | SIMPLE | a | ALL | NULL | NULL | NULL | NULL | 100395 | Using temporary; Using filesort | | 1 | SIMPLE | b | ref | order_id | order_id | 4 | test.a.order_id | 1 | | +----+-------------+-------+------+---------------+----------+---------+-----------------+--------+---------------------------------+ 2 rows in set (0.00 sec)Using where
當(dāng)有where子句時(shí),extra都會(huì)有說明。
Using sort_union(…)/Using union(…)/Using intersect(…)
下面的例子中user_id是一個(gè)檢索范圍,此時(shí)mysql會(huì)使用sort_union函數(shù)來進(jìn)行索引的合并。而當(dāng)user_id是一個(gè)固定值時(shí),請(qǐng)參看上面type說明index_merge的例子,此時(shí)會(huì)使用union函數(shù)進(jìn)行索引合并。
mysql> explain select * from t_order where order_id=100 or user_id>10; +----+-------------+---------+-------------+-----------------+-----------------+---------+------+------+------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------------+-----------------+-----------------+---------+------+------+------------------------------------------------+ | 1 | SIMPLE | t_order | index_merge | PRIMARY,user_id | user_id,PRIMARY | 5,4 | NULL | 2 | Using sort_union(user_id,PRIMARY); Using where | +----+-------------+---------+-------------+-----------------+-----------------+---------+------+------+------------------------------------------------+ 1 row in set (0.00 sec)對(duì)于Using intersect的例子可以參看下例,user_id與express_type發(fā)生了索引交叉合并。
mysql> explain select * from t_order where express_type=1 and user_id=100; +----+-------------+---------+-------------+----------------------+----------------------+---------+------+------+----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------------+----------------------+----------------------+---------+------+------+----------------------------------------------------+ | 1 | SIMPLE | t_order | index_merge | user_id,express_type | user_id,express_type | 5,1 | NULL | 1 | Using intersect(user_id,express_type); Using where | +----+-------------+---------+-------------+----------------------+----------------------+---------+------+------+----------------------------------------------------+ 1 row in set (0.00 sec)Using index for group-by
表明可以在索引中找到分組所需的所有數(shù)據(jù),不需要查詢實(shí)際的表。
mysql> explain select user_id from t_order group by user_id; +----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ | 1 | SIMPLE | t_order | range | NULL | user_id | 5 | NULL | 3 | Using index for group-by | +----+-------------+---------+-------+---------------+---------+---------+------+------+--------------------------+ 1 row in set (0.00 sec)除了上面的三個(gè)說明,還需要注意rows的數(shù)值,多行之間的數(shù)值是乘積的關(guān)系,可以估算大概要處理的行數(shù),如果乘積很大,那就很有優(yōu)化的必要了。
一個(gè)SQL語(yǔ)句的優(yōu)化例子
通過獲取EXPLAIN輸出的rows列中的值的乘積,可以很好地指示聯(lián)結(jié)(join)有多好。它大致告訴您MySQL必須檢查多少行才能執(zhí)行查詢。這是優(yōu)化SQL語(yǔ)句的前提依據(jù)。
下面的示例演示如何根據(jù)EXPLAIN提供的信息逐步優(yōu)化多表聯(lián)接。
假設(shè)您計(jì)劃使用EXPLAIN檢查下面SELECT語(yǔ)句:
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn,tt.ProjectReference, tt.EstimatedShipDate,tt.ActualShipDate, tt.ClientID,tt.ServiceCodes, tt.RepetitiveID,tt.CurrentProcess, tt.CurrentDPPerson,tt.RecordVolume, tt.DPPrinted, et.COUNTRY,et_1.COUNTRY, do.CUSTNAMEFROM tt, et, et AS et_1, doWHERE tt.SubmitTime IS NULLAND tt.ActualPC = et.EMPLOYIDAND tt.AssignedPC = et_1.EMPLOYIDAND tt.ClientID = do.CUSTNMBR;對(duì)于此示例,作出以下假設(shè):
- 相關(guān)列聲明如下:
| tt | ActualPC | CHAR(10) |
| tt | AssignedPC | CHAR(10) |
| tt | ClientID | CHAR(10) |
| et | EMPLOYID | CHAR(15) |
| do | CUSTNMBR | CHAR(15) |
- 相關(guān)表有如下的索引:
| tt | ActualPC |
| tt | AssignedPC |
| tt | ClientID |
| et | EMPLOYID (primary key) |
| do | CUSTNMBR (primary key) |
- 這個(gè) tt.ActualPC 值不是均勻分布的。
首先,在執(zhí)行任何優(yōu)化之前,使用EXPLAIN后會(huì)生成以下信息:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 do ALL PRIMARY NULL NULL NULL 2135 et_1 ALL PRIMARY NULL NULL NULL 74 tt ALL AssignedPC, NULL NULL NULL 3872ClientID,ActualPCRange checked for each record (index map: 0x23)由于每一個(gè)表對(duì)應(yīng)的type是ALL,所以這個(gè)輸出結(jié)果表明MySQL正在生成所有表的笛卡爾積,也就是說,表之間的每一行的組合。
稍微計(jì)算一下乘積,74×2135×74×3872=45268558720行。檢查這行數(shù)相當(dāng)耗時(shí)。如果表再大一點(diǎn),這將要等到猴年馬月啊。
針對(duì)這里的問題,如果將列聲明為相同的類型和大小,MySQL可以更有效地對(duì)列使用索引。在這個(gè)上下文中,如果VARCHAR和CHAR聲明為相同的大小,則它們被認(rèn)為是相同的。tt.ActualPC 的類型聲明為CHAR(10)并且 et.EMPLOYID 的是CHAR(15),因此長(zhǎng)度不匹配。
若要修復(fù)列長(zhǎng)度之間的差異,使用ALTER TABLE將ActualPC從10個(gè)字符延長(zhǎng)到15個(gè)字符(第一次修改):
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);修改后,再次執(zhí)行EXPLAIN生成如下結(jié)果:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 UsingClientID, whereActualPC do ALL PRIMARY NULL NULL NULL 2135Range checked for each record (index map: 0x1) et_1 ALL PRIMARY NULL NULL NULL 74Range checked for each record (index map: 0x1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1與上次的相比,縮小了74倍,差強(qiáng)人意。
在第二次修改,可以為tt.AssignedPC = et_1.EMPLOYID 和 tt.ClientID = do.CUSTNMBR 消除列之長(zhǎng)度差。
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),MODIFY ClientID VARCHAR(15);修改后,再次執(zhí)行EXPLAIN生成如下結(jié)果:
table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 UsingClientID, whereActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1到這里,該查詢幾乎盡可能被優(yōu)化了。接下來的問題是,MySQL默認(rèn)在tt.ActualPC列的值是均勻分布的,但tt表的其他列則不這么認(rèn)為。
幸運(yùn)的是,讓MySQL分析關(guān)鍵分布情況是很容易的:
mysql> ANALYZE TABLE tt;隨著額外的索引信息的幫助下,該SQL聯(lián)結(jié)接變得完美了。EXPLAIN生成如下結(jié)果:
table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 UsingClientID, whereActualPC et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1 et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1EXPLAIN輸出中的rows列是來自MySQL連接優(yōu)化器的有根據(jù)的推測(cè)。通過將rows乘積與查詢返回的實(shí)際行數(shù)進(jìn)行比較,檢查這些數(shù)字是否甚至接近真實(shí)值。如果數(shù)字相差很大,參閱官方文檔,獲取更多解決方案。Link
參考資料
總結(jié)
以上是生活随笔為你收集整理的MySQL关键字EXPLAIN的用法及其案例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《剑指Offer》23:链表中环的入口节
- 下一篇: 无限踩坑系列(6)-mySQL数据库链接