1. 程式人生 > >EXPLAIN sql優化方法(2) Using temporary ; Using filesort

EXPLAIN sql優化方法(2) Using temporary ; Using filesort

它的 In 默認 const join 產生 收藏 -c 意思

優化GROUP BY語句

默認情況下,MySQL對所有GROUP BY col1,col2...的字段進行排序。這與在查詢中指定ORDER BY col1,col2...類似。因此,如果顯式包括一個包含相同的列的ORDER BY子句,則對MySQL的實際執行性能沒有什麽影響。 如果查詢包括GROUP BY 但用戶想要避免排序結果的消耗,則可以指定ORDER By NULL禁止排序,例如:

Java代碼 技術分享圖片
  1. explain select id, sum(moneys) from sales2 group by id \G
  2. explain select id, sum(moneys) from sales2 group by id order by null
    \G
你可以通過比較發現第一條語句會比第二句在Extra:裏面多了Using filesort.而恰恰filesort是最耗時的。

優化ORDER BY語句

在某些情況中,MySQL可以使用一個索引來滿足ORDER BY子句,而不需要額外的排序。WHERE 條件和 ORDER BY使用相同的索引,並且ORDER BY的順序和索引順序相同,並且ORDER BY的字段都是升序或者都是降序。


例如:
Java代碼 技術分享圖片
  1. SELECT * FROM t1 ORDER BY key_part1,key_part2,....:
  2. SELECT * FROM t1 WHERE key_part1 = 1 ORDER BY key_part1 DESC,key_part2 DESC;
  3. SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
但是以下的情況不使用索引:
Java代碼 技術分享圖片
  1. SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
  2. --ORDER by的字段混合ASC 和 DESC
  3. SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
  4. ----用於查詢行的關鍵字與ORDER BY 中所使用的不相同
  5. SELECT * FROM t1 ORDER BY key1, key2;
  6. ----對不同的關鍵字使用ORDER BY
Java代碼 技術分享圖片
  1. 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 ;
  2. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+---------------------------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+---------------------------------+
  5. | 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 46585 | Using temporary ; Using filesort |
  6. | 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . catid | 1 | |
  7. | 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . sectionid | 1 | Using index |
  8. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+---------------------------------+
  9. 3 rows in set ( 0.00 sec )
  10. 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 ;
  11. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
  12. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  13. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
  14. | 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 46585 | Using filesort |
  15. | 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . catid | 1 | |
  16. | 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . sectionid | 1 | Using index |
  17. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+-------+----------------+
對於上面兩條語句,只是修改了一下排序字段,而第一個使用了Using temporary,而第二個卻沒有。在日常的網站維護中,如果有Using temporary出現,說明需要做一些優化措施了。

而為什麽第一個用了臨時表,而第二個沒有用呢?
因為如果有ORDER BY子句和一個不同的GROUP BY子句,或者如果ORDER BY或GROUP BY中的字段都來自其他的表而非連接順序中的第一個表的話,就會創建一個臨時表了。
那麽,對於上面例子中的第一條語句,我們需要對jos_categories的id進行排序,可以將SQL做如下改動:

Java代碼 技術分享圖片
  1. 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 ;
  2. +----+-------------+-------+--------+---------------+-----------+---------+-------------------------+------+----------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-------+--------+---------------+-----------+---------+-------------------------+------+----------------+
  5. | 1 | SIMPLE | A | ALL | NULL | NULL | NULL | NULL | 18 | Using filesort |
  6. | 1 | SIMPLE | B | ref | idx_catid | idx_catid | 4 | joomla_test . A . id | 3328 | |
  7. | 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . B . sectionid | 1 | Using index |
  8. +----+-------------+-------+--------+---------------+-----------+---------+-------------------------+------+----------------+
  9. 3 rows in set ( 0.00 sec )

這樣我們發現,不會再有Using temporary了,而且在查詢jos_content時,查詢的記錄明顯有了數量級的降低,這是因為jos_content的idx_catid起了作用。
所以結論是:

盡量對第一個表的索引鍵進行排序,這樣效率是高的。
我們還會發現,在排序的語句中都出現了Using filesort,字面意思可能會被理解為:使用文件進行排序或中文件中進行排序。實際上這是不正確的,這是一個讓人產生誤解的詞語。
當我們試圖對一個沒有索引的字段進行排序時,就是filesoft。它跟文件沒有任何關系,實際上是內部的一個快速排序。
然而,當我們回過頭來再看上面運行過的一個SQL的時候會有以下發現:

Java代碼 技術分享圖片
  1. 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 ;
  2. +----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
  5. | 1 | SIMPLE | C | index | PRIMARY | PRIMARY | 4 | NULL | 1 | Using index |
  6. | 1 | SIMPLE | A | ref | idx_catid , idx_section | idx_section | 4 | joomla_test . C . id | 23293 | Using where |
  7. | 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . catid | 1 | Using where |
  8. +----+-------------+-------+--------+-----------------------+-------------+---------+---------------------+-------+-------------+
  9. 3 rows in set ( 0.00 sec )

這是我們剛才運行過的一條語句,只是加了一個排序,而這條語句中C表的主鍵對排序起了作用,我們會發現Using filesort沒有了。
而盡管在上面的語句中也是對第一個表的主鍵進行排序,卻沒有得到想要的效果(第一個表的主鍵沒有用到),這是為什麽呢?實際上以上運行過的所有left join的語句中,第一個表的索引都沒有用到,盡管對第一個表的主鍵進行了排序也無濟於事。不免有些奇怪!

於是我們繼續測試了下一條SQL:

Java代碼 技術分享圖片
  1. 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 ;
  2. +----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+
  5. | 1 | SIMPLE | A | range | PRIMARY | PRIMARY | 4 | NULL | 90 | Using where |
  6. | 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . catid | 1 | |
  7. | 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . sectionid | 1 | Using index |
  8. +----+-------------+-------+--------+----------------+---------+---------+-------------------------+------+-------------+
  9. 3 rows in set ( 0.05 sec )

然後,當再次進行排序操作的時候,Using filesoft也沒有再出現

Java代碼 技術分享圖片
  1. 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 ;
  2. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+------+-------------+
  5. | 1 | SIMPLE | A | range | PRIMARY | PRIMARY | 4 | NULL | 105 | Using where |
  6. | 1 | SIMPLE | B | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . catid | 1 | |
  7. | 1 | SIMPLE | C | eq_ref | PRIMARY | PRIMARY | 4 | joomla_test . A . sectionid | 1 | Using index |
  8. +----+-------------+-------+--------+---------------+---------+---------+-------------------------+------+-------------+
  9. 3 rows in set ( 0.00 sec )

這個結果表明:對where條件裏涉及到的字段,Mysql會使用索引進行搜索,而這個索引的使用也對排序的效率有很好的提升。
寫了段程序測試了一下,分別讓以下兩個SQL語句執行200次:

Java代碼 技術分享圖片
  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的執行效率,並進行定期優化。

原文地址:http://hudeyong926.iteye.com/blog/785181

EXPLAIN sql優化方法(2) Using temporary ; Using filesort