一. EXPLAIN 語法
1. EXPLAIN tbl_name|SELECT select_options
EXPLAIN tbl_name和DESCRIBE tbl_name的作用是一樣的,用於顯示表結構等信息。
當我們在select語句前加上EXPLAIN後,Mysql將告訴我們它是如何處理select語句的,提供表之間的聯結方式、使用索引等有關信息。
二. 測試環境簡單介紹
為了節省創建表的時間,我用了joomla的文章表做測試,因為要演示優化過程,所以我事先刪除了表裡除主鍵之外的所有索引。
這裡用到了三個表: www.2cto.com
mysql> explain jos_content;
+------------------+------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+------------------+------+-----+---------------------+----------------+
| id | int(11) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | | |
| alias | varchar(255) | NO | | | |
| title_alias | varchar(255) | NO | | | |
| introtext | mediumtext | NO | | NULL | |
| fulltext | mediumtext | NO | | NULL | |
| state | tinyint(3) | NO | | 0 | |
| sectionid | int(11) unsigned | NO | | 0 | |
| mask | int(11) unsigned | NO | | 0 | |
| catid | int(11) unsigned | NO | | 0 | |
| created | datetime | NO | | 0000-00-00 00:00:00 | |
| created_by | int(11) unsigned | NO | | 0 | |
| created_by_alias | varchar(255) | NO | | | |
| modified | datetime | NO | | 0000-00-00 00:00:00 | |
| modified_by | int(11) unsigned | NO | | 0 | |
| checked_out | int(11) unsigned | NO | | 0 | |
| checked_out_time | datetime | NO | | 0000-00-00 00:00:00 | |
| publish_up | datetime | NO | | 0000-00-00 00:00:00 | |
| publish_down | datetime | NO | | 0000-00-00 00:00:00 | |
| images | text | NO | | NULL | |
| urls | text | NO | | NULL | |
| attribs | text | NO | | NULL | |
| version | int(11) unsigned | NO | | 1 | |
| parentid | int(11) unsigned | NO | | 0 | |
| ordering | int(11) | NO | | 0 | |
| metakey | text | NO | | NULL | |
| metadesc | text | NO | | NULL | |
| access | int(11) unsigned | NO | | 0 | |
| hits | int(11) unsigned | NO | | 0 | |
| metadata | text | NO | | NULL | |
+------------------+------------------+------+-----+---------------------+----------------+
30 rows in set (0.00 sec)
mysql> select count(*) from jos_content;
+----------+
| count(*) |
+----------+
| 46585 |
+----------+
1 row in set (0.00 sec)
mysql> desc jos_categories;
+------------------+---------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+---------------------+------+-----+---------------------+----------------+ www.2cto.com
| id | int(11) | NO | PRI | NULL | auto_increment |
| parent_id | int(11) | NO | | 0 | |
| title | varchar(255) | NO | | | |
| name | varchar(255) | NO | | | |
| alias | varchar(255) | NO | | | |
| image | varchar(255) | NO | | | |
| section | varchar(50) | NO | | | |
| image_position | varchar(30) | NO | | | |
| description | text | NO | | NULL | |
| published | tinyint(1) | NO | | 0 | |
| checked_out | int(11) unsigned | NO | | 0 | |
| checked_out_time | datetime | NO | | 0000-00-00 00:00:00 | |
| editor | varchar(50) | YES | | NULL | |
| ordering | int(11) | NO | | 0 | |
| access | tinyint(3) unsigned | NO | | 0 | |
| count | int(11) | NO | | 0 | |
| params | text | NO | | NULL | |
+------------------+---------------------+------+-----+---------------------+----------------+
17 rows in set (0.00 sec)
mysql> select count(*) from jos_categories;
+----------+
| count(*) |
+----------+
| 18 |
+----------+
1 row in set (0.00 sec)
mysql> desc jos_sections;
+------------------+---------------------+------+-----+---------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+---------------------+------+-----+---------------------+----------------+ www.2cto.com
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(255) | NO | | | |
| name | varchar(255) | NO | | | |
| alias | varchar(255) | NO | | | |
| image | text | NO | | NULL | |
| scope | varchar(50) | NO | | | |
| image_position | varchar(30) | NO | | | |
| description | text | NO | | NULL | |
| published | tinyint(1) | NO | | 0 | |
| checked_out | int(11) unsigned | NO | | 0 | |
| checked_out_time | datetime | NO | | 0000-00-00 00:00:00 | |
| ordering | int(11) | NO | | 0 | |
| access | tinyint(3) unsigned | NO | | 0 | |
| count | int(11) | NO | | 0 | |
| params | text | NO | | NULL | |
+------------------+---------------------+------+-----+---------------------+----------------+
15 rows in set (0.00 sec)
mysql> select count(*) from jos_sections;
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)
簡單說明:
jos_sections我們可以稱它為大類表,jos_categories為小類表,jos_content為文章表,三個表的記錄數分別為2、18、46585。
重要的字段基本上可以見名知義,只對幾個聯結字段說明一下:
jos_categories.secion: 這個字段是jos_section的主鍵id,它表明了大類和小類的父子關系,至於jos_categories.parent_id,我們可以先無視它的存在。
jos_content.sectionid: 這個字段也是jos_section的主鍵id,它表明了文章所屬的大類。
jos_content.catid: 這個字段是jos_categories的主鍵id,它表明了文章所屬的小類。
至於為什麼會用兩個表來存儲類別,而不用parent_id的方式實現無限級分類,我們也不必深究,因為這個就是joomla的結構,我們只是用來測試EXPLAIN而已。
三. 測試過程
下面我們說明如何使用EXPLAIN查看執行過程並對表結構和sql語句進行優化。
在下面的測試過程中,我會用一些看來根本就沒有意義的SQL語句,只是為了演示SQL語句結構和輸出效果,其可用價值我們大可不必過於追究,在實際生產中還要以實際應用為准。
mysql> explain select * from jos_content where id=16;
+----+-------------+-------------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | jos_content | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------------+-------+---------------+---------+---------+-------+------+-------+ www.2cto.com
1 row in set (0.00 sec)
因為使用了EXPLAIN關鍵字,所以Mysql為我們列出了一些查詢信息,它包含以下列:
id: SELECT的識別符,這是SELECT的查詢序列號。
select_type: SELECT類型,有以下幾種不同的類型
(1).SIMPLE: 簡單的SELECT(不使用UNION或子查詢)
(2).PRIMARY: 最外面的SELECT,如果我們使用UNION或子查詢,第一個查詢將會是這個類型
(3).UNION: 使用UNION查詢時,除第一個語句外的所有語句會返回這個類型
(4).DEPENDENT UNION: UNION中的第二個或後面的SELECT語句,取決於外面的查詢。
(5).UNION RESULT: UNION的結果。
(6).SUBQUERY: 子查詢中的第一個SELECT。
(7).DEPENDENT SUBQUERY: 子查詢中的第一個SELECT,取決於外面的查詢。
(8).DERIVED: 衍生表會返回這個類型。如:select * from (select * from jos_content) as A;。
table: 輸出引用的表。
type: 聯接類型,從這個選項我們可以初步判斷查詢效率,有以下幾種不同的類型(按從最佳到最壞排序):
(1).system: 表中僅有一行記錄,這是const的一個特例。
(2).const: 表中最多有一行符合查詢條件,它在查詢開始時被讀取。因為只有一行,這行的列值可被優化器剩余部分認為是常數。const表很快,因為它們只被讀取一次!(如上面的查詢)
(3).eq_ref: 對於每個來自於前面的表的行組合,從該表中讀取一行。例如:select * from A,B where A.id=B.id,如果id在B表中是unique或primary key,會返回這個類型。它是說對於A表中的每一行,在B表中讀取符合記錄的一行。除了const之外,這是最好的聯接類型。
(4).ref: 這個類型跟eq_ref類似,不同的是eq_ref能根據unique或主鍵在後面的表中選擇出唯一的行,而不能確定唯一行,則使用這個類型。
(5).ref_or_null: 該聯接類型如同ref,但是添加了MySQL 可以專門搜索包含NULL值的行。在解決子查詢中經常使用該聯接類型的優化。 www.2cto.com
(6).index_merge: 索引合並方法用於通過range掃描搜索行並將結果合成一個。合並會產生並集、交集或者正在進行的掃描的交集的並集。在EXPLAIN輸出中,該方法表現 為type列內的index_merge。在這種情況下,key列包含一列使用的索引,key_len包含這些索引的最長的關鍵元素。
(7).unique_subquery: unique_subquery是一個索引查找函數,可以完全替換子查詢,效率更高。explain select * from jos_content where id in (select id from jos_categories);會使用這個類型。
(8).index_subquery: 該聯接類型類似於unique_subquery。可以替換IN子查詢,但只適合子查詢中的非唯一索引。
(9).range: 只檢索給定范圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引。key_len包含所使用索引的最長關鍵元素。在該類型中ref列為NULL。 當使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比較關鍵字列時,可以使用這個類型。
(10).index: 這與ALL相同,除了只有索引樹被掃描。這通常比ALL快,因為索引文件通常比數據文件小。
(11).all: 對於每個來自於先前的表的行組合,將要做一個完整的表掃描。如果表格是第一個沒標記const的表,效果不是很好,並且在所有的其他情況下很差。你可以通過增加更多的索引來避免ALL,使得行能從早先的表中基於常數值或列值被檢索出來。
possible_keys: possible_keys列指出MySQL能使用哪個索引在該表中找到行。注意,該列完全獨立於EXPLAIN輸出所示的表的次序。這意味著在 possible_keys中的某些鍵實際上不能按生成的表次序使用。
如果該列是NULL,則沒有相關的索引。在這種情況下,可以通過檢查WHERE子句看是否它引用某些列或適合索引的列來提高你的查詢性能。如果是這樣,創造一個適當的索引並且再次用EXPLAIN檢查查詢。
key: key列顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。要想強制MySQL使用或忽視possible_keys列中的索 引,在查詢中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。對於MyISAM 和BDB 表,運行ANALYZE TABLE 可以幫助優化器選擇更好的索引。對於MyISAM 表,可以使用myisamchk --analyze。
key_len: 此列顯示MySQL決定使用的鍵長度。如果鍵是NULL,則長度為NULL。注意通過key_len值我們可以確定MySQL將實際使用一個多部關鍵字的幾個部分。在不損失精確性的情況下,長度越短越好。
ref: 此列顯示使用哪個列或常數與key一起從表中選擇行。
rows: 此列顯示了MySQL認為它執行查詢時必須檢查的行數。
Extra: 該列包含MySQL解決查詢的詳細信息。 www.2cto.com
(1).Distinct: 一旦MYSQL找到了與行相聯合匹配的行,就不再搜索了。
(2).Not exists: MYSQL 優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標准的行,就不再搜索了。
(3).Range checked for each: Record(index map:#)沒有找到理想的索引,因此對於從前面表中來的每一個行組合,MYSQL檢查使用哪個索引,並用它來從表中返回行。這是使用索引的最慢的連接之一。
(4).Using filesort: MYSQL需要進行額外的步驟來發現如何對返回的行排序。它根據連接類型以及存儲排序鍵值和匹配條件的全部行的行指針來排序全部行。
(5).Using index: 列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對表的全部的請求列都是同一個索引的部分的時候。
(6).Using temporary: 看到這個的時候,查詢需要優化了。這裡,MYSQL需要創建一個臨時表來存儲結果,這通常發生在對不同的列集進行ORDER BY上,而不是GROUP BY上。
(7).Using where: 使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,並且連接類型ALL或index,這就會發生,或者是查詢有問題。
上面的解釋,不必全部看懂,有些可以一帶而過,因為確實有些抽象,有些我也只是摘抄,但是下面我們用幾個具體的實例來更加深入地理解一下。
mysql> explain select A.id,A.title,B.title from jos_content A,jos_categories B where A.catid=B.id;
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+-------------+
| 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 46585 | |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+-------------+
2 rows in set (0.00 sec)
這個是我們經常使用的一種查詢方式,對B表的聯接類型使用了eq_ref,索引使用了PRIMARY,但是對於A表,卻沒有使用任何索引,這可能不是我們想要的。
查看以上SQL語句,我們可能會想到,有必要給A.catid加個索引了。
mysql> alter table jos_content add index idx_catid(`catid`);
Query OK, 46585 rows affected (0.75 sec) www.2cto.com
Records: 46585 Duplicates: 0 Warnings: 0
mysql> explain select A.id,A.title,B.title from jos_content A,jos_categories B where A.catid=B.id;
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+-------------+
| 1 | SIMPLE | A | ALL | idx_catid | NULL | NULL | NULL | 46585 | |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+-------------+
2 rows in set (0.00 sec)
這樣表A便使用了idx_catid索引。
下面我們做一次三個表的聯合查詢
mysql> explain select A.id,A.title,B.title from jos_content A,jos_categories B,jos_sections C where A.catid=B.id and A.sectionid=C.id;
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+--------------------------------+
| 1 | SIMPLE | C | index | PRIMARY | PRIMARY | 4 | NULL | 2 | Using index |
| 1 | SIMPLE | A | ALL | idx_catid | NULL | NULL | NULL | 46585 | Using where; Using join buffer |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+---------------------+-------+--------------------------------+
3 rows in set (0.00 sec)
這裡顯示了Mysql先將C表讀入查詢,並使用PRIMARY索引,然後聯合A表進行查詢,這時候type顯示的是ALL,可以用的索引有idx_catid,但是實際沒有用。 www.2cto.com
原因非常明顯,因為使用的連接條件是A.sectionid=C.id,所以我們給A.sectionid加個索引先。
mysql> alter table jos_content add index idx_section(`sectionid`);
Query OK, 46585 rows affected (0.89 sec)
Records: 46585 Duplicates: 0 Warnings: 0
mysql> explain select A.id,A.title,B.title from jos_content A,jos_categories B,jos_sections C where A.catid=B.id and A.sectionid=C.id;
+----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
| 1 | SIMPLE | C | index | PRIMARY | PRIMARY | 4 | NULL | 2 | Using index |
| 1 | SIMPLE | A | ref | idx_catid,idx_section | idx_section | 4 | joomla_test.C.id | 23293 | Using where |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | Using where |
+----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
3 rows in set (0.00 sec)
這時候顯示結果告訴我們,效果很明顯,在連接A表時type變成了ref,索引使用了idx_section,如果我們注意看後兩列,對A表的查詢結果後一次明顯少了一半左右,而且沒有用到join buffer。
這個表讀入的順序是Mysql優化器幫我們做的,可以得知,用記錄數少的表做為基礎表進行聯合,將會得到更高的效率。
對於上面的語句,我們換一種寫法
mysql> explain select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id;
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+-------------+
| 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 46585 | |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | |
| 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.sectionid | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+-------------+ www.2cto.com
3 rows in set (0.00 sec)
Mysql 讀入表的順序被改變了,這意味著,如果我們用left join來做連接查詢,Mysql會按SQL語句中表出現的順序讀入,還有一個有變化的地方是聯接B和C的type都變成了eq_ref,前邊我們說過, 這樣說明Mysql可以找到唯一的行,這個效率是比ref要高的。
再來看一個排序的例子:
mysql> explain select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id order by B.id;
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+---------------------------------+
| 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 46585 | Using temporary; Using filesort |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | |
| 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.sectionid | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+---------------------------------+
3 rows in set (0.00 sec)
mysql> explain select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id order by A.id;
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | www.2cto.com
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
| 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 46585 | Using filesort |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | |
| 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.sectionid | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
對於上面兩條語句,只是修改了一下排序字段,而第一個使用了Using temporary,而第二個卻沒有。在日常的網站維護中,如果有Using temporary出現,說明需要做一些優化措施了。
而為什麼第一個用了臨時表,而第二個沒有用呢?
因為如果有ORDER BY子句和一個不同的GROUP BY子句,或者如果ORDER BY或GROUP BY中的字段都來自其他的表而非連接順序中的第一個表的話,就會創建一個臨時表了。
那麼,對於上面例子中的第一條語句,我們需要對jos_categories的id進行排序,可以將SQL做如下改動:
mysql> explain select B.id,B.title,A.title from jos_categories A left join jos_content B on A.id=B.catid left join jos_sections C on B.sectionid=C.id order by A.id;
+----+-------------+-------+--------+---------------+-----------+---------+-------------------------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+-----------+---------+-------------------------+------+----------------+
| 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 18 | Using filesort |
| 1 | SIMPLE | B | ref | idx_catid | idx_catid | 4 | joomla_test.A.id | 3328 | |
| 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.B.sectionid | 1 | Using index |
+----+-------------+-------+--------+---------------+-----------+---------+-------------------------+------+----------------+
3 rows in set (0.00 sec)
這樣我們發現,不會再有Using temporary了,而且在查詢jos_content時,查詢的記錄明顯有了數量級的降低,這是因為jos_content的idx_catid起了作用。 www.2cto.com
所以結論是:盡量對第一個表的索引鍵進行排序,這樣效率是高的。
我們還會發現,在排序的語句中都出現了Using filesort,字面意思可能會被理解為:使用文件進行排序或中文件中進行排序。實際上這是不正確的,這是一個讓人產生誤解的詞語。
當我們試圖對一個沒有索引的字段進行排序時,就是filesoft。它跟文件沒有任何關系,實際上是內部的一個快速排序。
然而,當我們回過頭來再看上面運行過的一個SQL的時候會有以下發現:
mysql> explain select A.id,A.title,B.title from jos_content A,jos_categories B,jos_sections C where A.catid=B.id and A.sectionid=C.id order by C.id;
+----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
| 1 | SIMPLE | C | index | PRIMARY | PRIMARY | 4 | NULL | 1 | Using index |
| 1 | SIMPLE | A | ref | idx_catid,idx_section | idx_section | 4 | joomla_test.C.id | 23293 | Using where |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | Using where |
+----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
3 rows in set (0.00 sec)
這是我們剛才運行過的一條語句,只是加了一個排序,而這條語句中C表的主鍵對排序起了作用,我們會發現Using filesort沒有了。
而盡管在上面的語句中也是對第一個表的主鍵進行排序,卻沒有得到想要的效果(第一個表的主鍵沒有用到),這是為什麼呢?實際上以上運行過的所有left join的語句中,第一個表的索引都沒有用到,盡管對第一個表的主鍵進行了排序也無濟於事。不免有些奇怪!
於是我們繼續測試了下一條SQL:
mysql> explain select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id where A.id < 100;
+----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+ www.2cto.com
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | A | range | PRIMARY | PRIMARY | 4 | NULL | 90 | Using where |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | |
| 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.sectionid | 1 | Using index |
+----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+
3 rows in set (0.05 sec)
然後,當再次進行排序操作的時候,Using filesoft也沒有再出現
mysql> explain select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id where A.id < 100 order by A.id;
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+------+-------------+
| 1 | SIMPLE | A | range | PRIMARY | PRIMARY | 4 | NULL | 105 | Using where |
| 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.catid | 1 | |
| 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test.A.sectionid | 1 | Using index |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+------+-------------+
3 rows in set (0.00 sec) www.2cto.com
這個結果表明:對where條件裡涉及到的字段,Mysql會使用索引進行搜索,而這個索引的使用也對排序的效率有很好的提升。
寫了段程序測試了一下,分別讓以下兩個SQL語句執行200次:
1. select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id
2. select A.id,A.title,B.title from jos_content A,jos_categories B,jos_sections C where A.catid=B.id and A.sectionid=C.id
3. select A.id,A.title,B.title from jos_content A left join jos_categories B on A.catid=B.id left join jos_sections C on A.sectionid=C.id order by rand() limit 10
4. select A.id from jos_content A left join jos_categories B on B.id=A.catid left join jos_sections C on A.sectionid=C.id order by A.id
結果是第(1)條平均用時20s,第(2)條平均用時44s,第(3)條平均用時70s,第(4)條平均用時2s。而且假如我們用explain觀察第(3)條語句的執行情況,會發現它創建了temporary表來進行排序。
綜上所述,可以得出如下結論:
1. 對需要查詢和排序的字段要加索引。
2. 在一定環境下,left join還是比普通連接查詢效率要高,但是要盡量少地連接表,並且在做連接查詢時注意觀察索引是否起了作用。
3. 排序盡量對第一個表的索引字段進行,可以避免mysql創建臨時表,這是非常耗資源的。
4. 對where條件裡涉及到的字段,應適當地添加索引,這樣會對排序操作有優化的作用。
5. 在做隨機抽取數據的需求時,避免使用order by rand(),從上面的例子可以看出,這種是很浪費數據庫資源的,在執行過程中用show processlist查看,會發現第(3)條有Copying to tmp table on disk。而對(3)和(4)的對比得知,如果要實現這個功能,最好另辟奚徑,來減輕Mysql的壓力。
6. 從第4點可以看出,如果說在分頁時我們能先得到主鍵,再根據主鍵查詢相關內容,也能得到查詢的優化效果。通過國外《High Performance MySQL》專家組的測試可以看出,根據主鍵進行查詢的類似“SELECT ... FROM... WHERE id = ...”的SQL語句(其中id為PRIMARYKEY),每秒鐘能夠處理10000次以上的查詢,而普通的SELECT查詢每秒只能處理幾十次到幾百次。涉及到分頁的查詢效率問題,網上的可用資源越來越多,查詢功能也體現出了它的重要性。也便是sphinx、lucene這些第三方搜索引擎的用武之地了。 www.2cto.com
7. 在平時的作業中,可以打開Mysql的Slow queries功能,經常檢查一下是哪些語句降低的Mysql的執行效率,並進行定期優化。
作者 yangemil