從Mysql EXPLAIN探尋數據庫查詢優化2

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的執行效率,並進行定期優化。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章