天天看點

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

優化group by語句

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

explain select id, sum(moneys) from sales2 group by id \g     

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的字段都是升序或者都是降序。

例如:

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

select * from t1 order by key_part1,key_part2,....:  

select * from t1 where key_part1 = 1 order by key_part1 desc,key_part2 desc;  

select * from t1 order by key_part1 desc, key_part2 desc;  

但是以下的情況不使用索引:

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

select * from t1 order by key_part1 desc, key_part2 asc;  

--order by的字段混合asc 和 desc  

select * from t1 where key2=constant order by key1;  

 ----用于查詢行的關鍵字與order by 中所使用的不相同  

 select * from t1 order by key1, key2;  

 ----對不同的關鍵字使用order by  

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

 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做如下改動:

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

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     |  

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

是以結論是:

盡量對第一個表的索引鍵進行排序,這樣效率是高的。 

我們還會發現,在排序的語句中都出現了using filesort,字面意思可能會被了解為:使用檔案進行排序或中檔案中進行排序。實際上這是不正确的,這是一個讓人産生誤解的詞語。 

當我們試圖對一個沒有索引的字段進行排序時,就是filesoft。它跟檔案沒有任何關系,實際上是内部的一個快速排序。 

然而,當我們回過頭來再看上面運作過的一個sql的時候會有以下發現:

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

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 |  

這是我們剛才運作過的一條語句,隻是加了一個排序,而這條語句中c表的主鍵對排序起了作用,我們會發現using filesort沒有了。

而盡管在上面的語句中也是對第一個表的主鍵進行排序,卻沒有得到想要的效果(第一個表的主鍵沒有用到),這是為什麼呢?實際上以上運作過的所有left join的語句中,第一個表的索引都沒有用到,盡管對第一個表的主鍵進行了排序也無濟于事。不免有些奇怪!

于是我們繼續測試了下一條sql:

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

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也沒有再出現

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

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 |  

這個結果表明:對where條件裡涉及到的字段,mysql會使用索引進行搜尋,而這個索引的使用也對排序的效率有很好的提升。

寫了段程式測試了一下,分别讓以下兩個sql語句執行200次:

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

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  

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  

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  

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條件裡涉及到的字段,應适當地添加索引,這樣會對排序操作有優化的作用。

6. 從第4點可以看出,如果說在分頁時我們能先得到主鍵,再根據主鍵查詢相關内容,也能得到查詢的優化效果。通過國外《high performance mysql》專家組的測試可以看出,根據主鍵進行查詢的類似“select ... from... where id = ...”的sql語句(其中id為primarykey),每秒鐘能夠處理10000次 以上的查詢,而普通的select查詢每秒隻能處理幾十次到幾百次 。涉及到分頁的查詢效率問題,網上的可用資源越來越多,查詢功能也展現出了它的重要性。也便是sphinx、lucene這些第三方搜尋引擎的用武之地了。

7. 在平時的作業中,可以打開mysql的slow queries功能,經常檢查一下是哪些語句降低的mysql的執行效率,并進行定期優化。