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)
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,但是實際沒有用。
原因非常明顯,因爲使用的連接條件是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 |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+-------------+
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 |
+----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
| 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起了作用。
所以結論是:儘量對第一個表的索引鍵進行排序,這樣效率是高的。
我們還會發現,在排序的語句中都出現了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;
+----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+
| 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)
這個結果表明:對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這些第三方搜索引擎的用武之地了。
7. 在平時的作業中,可以打開Mysql的Slow queries功能,經常檢查一下是哪些語句降低的Mysql的執行效率,並進行定期優化。