MySQL性能優(yōu)化之索引優(yōu)化

1 MySQL索引簡介

1.1 索引概念

索引是對數(shù)據(jù)庫表中一列或多列的值進行排序的一種結(jié)構(gòu),使用索引可快速訪問數(shù)據(jù)庫表中的特定信息。
索引的一個主要目的就是加快檢索表中數(shù)據(jù)的方法,亦即能協(xié)助信息搜索者盡快的找到符合限制條件的記錄ID的輔助數(shù)據(jù)結(jié)構(gòu)。
直接說索引概念很難理解,這里有一個很形象的例子:
索引就好比一本書的目錄,它會讓你更快的找到內(nèi)容。
但是很顯然,目錄(索引)并不是越多越好,假如這本書1000頁,有500頁是目錄,效率仍然很低。另外,目錄是要占紙張的,而索引是要占磁盤空間的。
與書的目錄不同的是,數(shù)據(jù)庫的索引可以有多個。

1.2 索引結(jié)構(gòu)

Mysql索引主要有兩種結(jié)構(gòu):B+Tree索引和Hash索引.

1.2.1 Hash索引

MySQL中,只有Memory(Memory表只存在內(nèi)存中,斷電會消失,適用于臨時表)存儲引擎顯式支持Hash索引,是Memory表的默認索引類型。Memory表也可以使用B-Tree索引。Hsah索引把數(shù)據(jù)的索引以hash形式組織起來,因此當查找某一條記錄的時候,速度非???。由于Hsah索引是hash結(jié)構(gòu),每個鍵只對應(yīng)一個值,而且是散列的方式分布,所以并不支持范圍查找和排序等功能。

1.2.2 B+Tree索引

B-Tree是MySQL使用最頻繁的一個索引數(shù)據(jù)結(jié)構(gòu),是InnoDB和MyISAM存儲引擎的索引類型。相對Hash索引,B-Tree索引在查找單條記錄的速度比不上Hash索引,但是因為更適合排序等操作,所以更受用戶的歡迎。

  • 帶順序訪問指針的B-Tree

B-Tree所有索引數(shù)據(jù)都在葉子結(jié)點上,并且增加了順序訪問指針,每個葉子節(jié)點都有指向相鄰葉子節(jié)點的指針。這樣做是為了提高區(qū)間查詢效率。例如查詢key為從100到200的所有數(shù)據(jù)記錄,當找到100后,只需順著節(jié)點和指針順序遍歷就可以一次性訪問到所有數(shù)據(jù)節(jié)點,極大提到了區(qū)間查詢效率。

  • 大大減少磁盤I/O讀取

數(shù)據(jù)庫系統(tǒng)的設(shè)計者巧妙利用了磁盤預讀原理,將一個節(jié)點的大小設(shè)為等于一個頁,這樣每個節(jié)點只需要一次I/O就可以完全載入。
為了達到這個目的,在實際實現(xiàn)B+Tree還使用如下技巧:
(1)每次新建節(jié)點時,直接申請一個頁的空間,這樣就保證一個節(jié)點物理上也存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,就實現(xiàn)了一個node只需一次I/O。

(2)B-Tree中一次檢索最多需要h-1次I/O(根節(jié)點常駐內(nèi)存),漸進復雜度為O(h)=O(logdN)。一般實際應(yīng)用中,出度d是非常大的數(shù)字,通常超過100,因此h非常?。ㄍǔ2怀^3)。而紅黑樹這種結(jié)構(gòu),h明顯要深的多。由于邏輯上很近的節(jié)點(父子)物理上可能很遠,無法利用局部性,所以紅黑樹的I/O漸進復雜度也為O(h),效率明顯比B-Tree差很多。

1.3 索引的類型

1.3.1 選擇索引的數(shù)據(jù)類型

MySQL支持很多數(shù)據(jù)類型,選擇合適的數(shù)據(jù)類型存儲數(shù)據(jù)對性能有很大的影響。通常來說,可以遵循以下一些指導原則:

  1. 越小的數(shù)據(jù)類型通常更好
    越小的數(shù)據(jù)類型通常在磁盤、內(nèi)存和CPU緩存中都需要更少的空間,處理起來更快。
  2. 簡單的數(shù)據(jù)類型更好
    整型數(shù)據(jù)比起字符,處理開銷更小,因為字符串的比較更復雜。在MySQL中,應(yīng)該用內(nèi)置的日期和時間數(shù)據(jù)類型,而不是用字符串來存儲時間;以及用整型數(shù)據(jù)類型存儲IP地址。
  3. 盡量避免NULL
    應(yīng)該指定列為NOT NULL,除非你想存儲NULL。在MySQL中,含有空值的列很難進行查詢優(yōu)化,因為它們使得索引、索引的統(tǒng)計信息以及比較運算更加復雜。你應(yīng)該用0、一個特殊的值或者一個空串代替空值。

1.3.2 選擇主鍵類型

選擇合適的標識符是非常重要的。選擇時不僅應(yīng)該考慮存儲類型,而且應(yīng)該考慮MySQL是怎樣進行運算和比較的。一旦選定數(shù)據(jù)類型,應(yīng)該保證所有相關(guān)的表都使用相同的數(shù)據(jù)類型。

  1. 整型
    通常是作為標識符的最好選擇,因為可以更快的處理,而且可以設(shè)置為AUTO_INCREMENT。

  2. 字符串
    盡量避免使用字符串作為標識符,它們消耗更多的空間,處理起來也較慢。而且,通常來說,字符串都是隨機的,所以它們在索引中的位置也是隨機的,這會導致頁面分裂、隨機訪問磁盤,聚簇索引分裂(對于使用聚簇索引的存儲引擎)。

1.3.3 常用的索引

常用的索引有以下類型:
主鍵索引、唯一索引、普通索引、全文索引、組合索引
常用索引之間的區(qū)別如下:

索引類型 特點
普通索引 最基本的索引,沒有任何限制
唯一索引 與"普通索引"類似,不同的就是:索引列的值必須唯一,但允許有空值。
主鍵索引 它是一種特殊的唯一索引,不允許有空值。
全文索引 僅可用于 MyISAM 表,針對較大的數(shù)據(jù),生成全文索引很耗時好空間。
組合索引 為了更多的提高mysql效率可建立組合索引,遵循”最左前綴“原則。創(chuàng)建復合索引時應(yīng)該將最常用(頻率)作限制條件的列放在最左邊,依次遞減。

!備注 組合索引最左字段用in是可以用到索引的。

1.3.4 創(chuàng)建索引示例

索引類型 特點
PRIMARY KEY(主鍵索引) ALTER TABLE table_name ADD PRIMARY KEY ( col )
UNIQUE(唯一索引) ALTER TABLE table_name ADD UNIQUE (col)
INDEX(普通索引) ALTER TABLE table_name ADD INDEX index_name (col)
FULLTEXT(全文索引) ALTER TABLE table_name ADD FULLTEXT ( col )
組合索引 ALTER TABLE table_name ADD INDEX index_name (col1, col2, col3 )

2 索引優(yōu)化

每當創(chuàng)建完數(shù)據(jù)庫表和索引之后,以應(yīng)該問自己以下幾個問題:

  • 創(chuàng)建的索引是否能夠滿足絕大多數(shù)的查詢?
  • 索引的區(qū)分度是否足夠大?
  • 組合索引的字段是否過多?是否存在區(qū)分度不高的字段?
  • 索引有沒有提高效率的空間?
    前三個問題,一般憑借DBA或者開發(fā)人員的經(jīng)驗來進行甄別,或者在實際測試或者使用中進行優(yōu)化。
    但是索引有沒有提高效率的空間?這個問題是可以通過MySQL的explain命令來進行優(yōu)化指導的。

2.1 explain的作用

explain顯示了mysql如何使用索引來處理select語句以及連接表??梢詭椭x擇更好的索引和寫出更優(yōu)化的查詢語句。
使用方法,在select語句前加上explain就可以了。

explain select name,age,gender from tableA,tableB where tableA.id=tableB .id 

2.2 explain列的解釋

例如:


圖1 explain命令結(jié)果
名稱 含義
table 顯示這一行的數(shù)據(jù)是關(guān)于哪張表的;
type type顯示的是訪問類型,是較為重要的一個指標,結(jié)果值從好到壞依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。一般來說,得保證查詢至少達到range級別,最好能達到ref。
possible_keys 顯示可能應(yīng)用在這張表中的索引。如果為空,沒有可能的索引??梢詾橄嚓P(guān)的域從WHERE語句中選擇一個合適的語句
key 實際使用的索引。如果為NULL,則沒有使用索引。很少的情況下,MYSQL會選擇優(yōu)化不足的索引。這種情況下,可以在SELECT語句中使用USE INDEX(indexname)來強制使用一個索引或者用IGNORE INDEX(indexname)來強制MYSQL忽略索引
key_len 使用的索引的長度。在不損失精確性的情況下,長度越短越好
ref 顯示索引的哪一列被使用了,如果可能的話,是一個常數(shù)
rows MYSQL認為必須檢查的用來返回請求數(shù)據(jù)的行數(shù)
Extra 關(guān)于MYSQL如何解析查詢的額外信息。將在下表中討論,但這里可以看到的壞的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,結(jié)果是檢索會很慢

extra列返回的描述的意義

名稱 含義
Distinct 一旦MYSQL找到了與行相聯(lián)合匹配的行,就不再搜索了
Not exists MYSQL優(yōu)化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標準的行,就不再搜索了
Range checked for each Record(index map:#) 沒有找到理想的索引,因此對于從前面表中來的每一個行組合,MYSQL檢查使用哪個索引,并用它來從表中返回行。這是使用索引的最慢的連接之一
Using filesort 看到這個的時候,查詢就需要優(yōu)化了。MYSQL需要進行額外的步驟來發(fā)現(xiàn)如何對返回的行排序。它根據(jù)連接類型以及存儲排序鍵值和匹配條件的全部行的行指針來排序全部行
Using index 列數(shù)據(jù)是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發(fā)生在對表的全部的請求列都是同一個索引的部分的時候
Usingtemporary 看到這個的時候,查詢需要優(yōu)化了。這里,MYSQL需要創(chuàng)建一個臨時表來存儲結(jié)果,這通常發(fā)生在對不同的列集進行ORDER BY上,而不是GROUP BY上Where used 使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,并且連接類型ALL或index,這就會發(fā)生,或者是查詢有問題不同連接類型的解釋(按照效率高低的順序排序)
system 表只有一行:system表。這是const連接類型的特殊情況
const 表中的一個記錄的最大值能夠匹配這個查詢(索引可以是主鍵或惟一索引)。因為只有一行,這個值實際就是常數(shù),因為MYSQL先讀這個值然后把它當做常數(shù)來對待
eq_ref 在連接中,MYSQL在查詢時,從前面的表中,對每一個記錄的聯(lián)合都從表中讀取一個記錄,它在查詢使用了索引為主鍵或惟一鍵的全部時使用
ref 這個連接類型只有在查詢使用了不是惟一或主鍵的鍵或者是這些類型的部分(比如,利用最左邊前綴)時發(fā)生。對于之前的表的每一個行聯(lián)合,全部記錄都將從表中讀出。這個類型嚴重依賴于根據(jù)索引匹配的記錄多少—越少越好
range 這個連接類型使用索引返回一個范圍中的行,比如使用>或<查找東西時發(fā)生的情況
index 這個連接類型對前面的表中的每一個記錄聯(lián)合進行完全掃描(比ALL更好,因為索引一般小于表數(shù)據(jù))
ALL 這個連接類型對于前面的每一個記錄聯(lián)合進行完全掃描,這一般比較糟糕,應(yīng)該盡量避免

2.3 示例

2.3.1 實例1

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 | 100453 |       | 
+----+-------------+---------+------+---------------+------+---------+------+--------+-------+ 
1 row in set (0.03 sec) 

加上extended后之后:

mysql> explain extended select * from t_order; 
+----+-------------+---------+------+---------------+------+---------+------+--------+----------+-------+ 
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra | 
+----+-------------+---------+------+---------------+------+---------+------+--------+----------+-------+ 
|  1 | SIMPLE      | t_order | ALL  | NULL          | NULL | NULL    | NULL | 100453 |   100.00 |       | 
+----+-------------+---------+------+---------------+------+---------+------+--------+----------+-------+ 
1 row in set, 1 warning (0.00 sec) 

表格里每一列的含義如下:

名稱 含義
id SELECT識別符。這是SELECT的查詢序列號
select_type SELECT類型,可以為以下任何一種:SIMPLE:簡單SELECT(不使用UNION或子查詢);PRIMARY:最外面的SELECT;UNION:UNION中的第二個或后面的SELECT語句;DEPENDENT UNION:UNION中的第二個或后面的SELECT語句,取決于外面的查詢;UNION RESULT:UNION 的結(jié)果;SUBQUERY:子查詢中的第一個SELECT;DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決于外面的查詢;DERIVED:導出表的SELECT(FROM子句的子查詢);
table 輸出的行所引用的表
type 聯(lián)接類型。下面給出各種聯(lián)接類型,按照從最佳類型到最壞類型進行排序:
possible_keys 指出MySQL能使用哪個索引在該表中找到行
key 顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。
key_len 顯示MySQL決定使用的鍵長度。如果鍵是NULL,則長度為NULL。
ref 顯示使用哪個列或常數(shù)與key一起從表中選擇行。
rows 顯示MySQL認為它執(zhí)行查詢時必須檢查的行數(shù)。多行之間的數(shù)據(jù)相乘可以估算要處理的行數(shù)。
filtered 顯示了通過條件過濾出的行數(shù)的百分比估計值。
Extra 該列包含MySQL解決查詢的詳細信息。具體如下:

type類型

名稱 含義
system 表僅有一行(=系統(tǒng)表)。這是const聯(lián)接類型的一個特例。
const 表最多有一個匹配行,它將在查詢開始時被讀取。因為僅有一行,在這行的列值可被優(yōu)化器剩余部分認為是常數(shù)。const表很快,因為它們只讀取一次!
eq_ref 對于每個來自于前面的表的行組合,從該表中讀取一行。這可能是最好的聯(lián)接類型,除了const類型。
ref 對于每個來自于前面的表的行組合,所有有匹配索引值的行將從這張表中讀取。
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是一個索引查找函數(shù),可以完全替換子查詢,效率更高。
index_subquery 該聯(lián)接類型類似于unique_subquery??梢蕴鎿QIN子查詢,但只適合下列形式的子查詢中的非唯一索引: value IN (SELECT key_column FROM single_table WHERE some_expr)
range 只檢索給定范圍的行,使用一個索引來選擇行。
index 該聯(lián)接類型與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引文件通常比數(shù)據(jù)文件小。
ALL 對于每個來自于先前的表的行組合,進行完整的表掃描。

extra

名稱 含義
Distinct MySQL發(fā)現(xiàn)第1個匹配行后,停止為當前的行組合搜索更多的行。
Not exists MySQL能夠?qū)Σ樵冞M行LEFT JOIN優(yōu)化,發(fā)現(xiàn)1個匹配LEFT JOIN標準的行后,不再為前面的的行組合在該表內(nèi)檢查更多的行。
range checked for each record (index map: #) MySQL沒有發(fā)現(xiàn)好的可以使用的索引,但發(fā)現(xiàn)如果來自前面的表的列值已知,可能部分索引可以使用。
Using filesort MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行。
Using index 從只使用索引樹中的信息而不需要進一步搜索讀取實際的行來檢索表中的列信息。
Using temporary 為了解決查詢,MySQL需要創(chuàng)建一個臨時表來容納結(jié)果。
Using where WHERE 子句用于限制哪一個行匹配下一個表或發(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)了一個索引,可以用來查 詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜索硬盤訪問實際的表。

2.3.2 select_type的說明

  1. UNION
    通過union來連接多個查詢結(jié)果時,第二個之后的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) 
  1. DEPENDENT UNION與DEPENDENT SUBQUERY
    當union作為子查詢時,其中第二個union的select_type就是DEPENDENT UNION。
    第一個子查詢的select_type則是DEPENDENT SUBQUERY。
mysql> explain select * from t_order where order_id in (select order_id from t_order where order_id=100 union select order_id from t_order where order_id=200); 
+----+--------------------+------------+-------+---------------+---------+---------+-------+--------+-------------+ 
| id | select_type        | table      | type  | possible_keys | key     | key_len | ref   | rows   | Extra       | 
+----+--------------------+------------+-------+---------------+---------+---------+-------+--------+-------------+ 
|  1 | PRIMARY            | t_order    | ALL   | NULL          | NULL    | NULL    | NULL  | 100453 | Using where | 
|  2 | DEPENDENT SUBQUERY | t_order    | const | PRIMARY       | PRIMARY | 4       | const |      1 | Using index | 
|  3 | DEPENDENT UNION    | t_order    | const | PRIMARY       | PRIMARY | 4       | const |      1 | Using index | 
| NULL | UNION RESULT       | <union2,3> | ALL   | NULL          | NULL    | NULL    | NULL  |   NULL |             | 
+----+--------------------+------------+-------+---------------+---------+---------+-------+--------+-------------+ 
4 rows in set (0.03 sec) 
  1. SUBQUERY
    子查詢中的第一個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) 
  1. DERIVED
    當子查詢是from子句時,其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) 

2.3.3 type的說明

  1. system,const

見上面4.DERIVED的例子。其中第一行的type就是為system,第二行是const,這兩種聯(lián)接類型是最快的。

  1. eq_ref

在t_order表中的order_id是主鍵,t_order_ext表中的order_id也是主鍵,該表可以認為是訂單表的補充信息表,他們的關(guān)系是1對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) 
  1. ref

下面的例子在上面的例子上略作了修改,加上了條件。此時b表的聯(lián)接類型變成了ref。因為所有與a表中order_id=100的匹配記錄都將會從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) 
  1. ref_or_null

user_id字段是一個可以為空的字段,并對該字段創(chuàng)建了一個索引。在下面的查詢中可以看到聯(lián)接類型為ref_or_null,這是mysql為含有null的字段專門做的處理。在我們的表設(shè)計中應(yīng)當盡量避免索引字段為NULL,因為這會額外的耗費mysql的處理時間來做優(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) 
  1. index_merge

經(jīng)常出現(xiàn)在使用一張表中的多個索引時。mysql會將多個索引合并在一起,如下例:

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) 
  1. unique_subquery

該聯(lián)接類型用于替換value IN (SELECT primary_key FROM single_table WHERE some_expr)這樣的子查詢的ref。注意ref列,其中第二行顯示的是func,表明unique_subquery是一個函數(shù),而不是一個普通的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) 
  1. 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) 
  1. range

按指定的范圍進行檢索,很常見。

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) 
  1. index

在進行統(tǒng)計時非常常見,此聯(lián)接類型實際上會掃描索引樹,僅比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) 
  1. 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) 

2.3.4 extra的說明

  1. Distinct

MySQL發(fā)現(xiàn)第1個匹配行后,停止為當前的行組合搜索更多的行。對于此項沒有找到合適的例子,求指點。

  1. Not exists

因為b表中的order_id是主鍵,不可能為NULL,所以mysql在用a表的order_id掃描t_order表,并查找b表的行時,如果在b表發(fā)現(xiàn)一個匹配的行就不再繼續(xù)掃描b了,因為b表中的order_id字段不可能為NULL。這樣避免了對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) 
  1. 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)
  1. Using filesort

在有排序子句的情況下很常見的一種情況。此時mysql會根據(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) 
  1. Using index

這是性能很高的一種情況。當查詢所需的數(shù)據(jù)可以直接從索引樹中檢索到時,就會出現(xiàn)。上面的例子中有很多這樣的例子,不再多舉例了。

  1. Using temporary

發(fā)生這種情況一般都是需要進行優(yōu)化的。mysql需要創(chuàng)建一張臨時表用來處理此類查詢。

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) 
  1. Using where

當有where子句時,extra都會有說明。

  1. Using sort_union(...)/Using union(...)/Using intersect(...)

下面的例子中user_id是一個檢索范圍,此時mysql會使用sort_union函數(shù)來進行索引的合并。而當user_id是一個固定值時,請參看上面type說明5.index_merge的例子,此時會使用union函數(shù)進行索引合并。

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) 

對于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) 
  1. Using index for group-by

表明可以在索引中找到分組所需的所有數(shù)據(jù),不需要查詢實際的表。

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)
除了上面的三個說明,還需要注意rows的數(shù)值,多行之間的數(shù)值是乘積的關(guān)系,可以估算大概要處理的行數(shù),如果乘積很大,那就很有優(yōu)化的必要了。

參考列表

  1. mysql explain用法和結(jié)果的含義

  2. Mysql索引介紹及常見索引的區(qū)別

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容