1. 程式人生 > >kevin_xiang的專欄,mail: [email protec

kevin_xiang的專欄,mail: [email protec

確實,在檢視定義比較複雜的情況下,要對檢視操作進行有效的優化是非常困難的。因此在這個時候,MySQL使用了一種以不變應萬變的方法,即先執行檢視定義,將其結果使用臨時表儲存起來,這樣後續對檢視的操作就轉化為對臨時表的操作。不能不說從單從軟體設計的角度看,這樣的方法非常的優雅,然而從效能角度,這一方法也是非常的差。

比如我們希望使用如下的檢視來表示每個使用者的評論數,即:
CREATE VIEW comment_count AS SELECT user_id, count(*) AS count FROM comment GROUP BY user_id;
使用這個檢視的時候,我們可能心裡有個小算盤。目前我們先用這個檢視頂著,如果效能確實有問題,那我們就再來搞一張comment_count的表,其中就記下來每個使用者的評論數。而我們現在先用這個檢視是為了將來要是改的話會方便點(這也是檢視--即教科書中所謂的外模式--這個東西存在的主要原因之一,另一主要原因是便於許可權控制)。但是遇到了MySQL這個蠢貨,我們的算盤鐵定會失敗。

我們來看一下指定user_id從comment_count選取記錄時的執行策略:
mysql> explain select count(*) from comment_count where user_id = 90;
+----+-------------+------------+-------+---------------+-----------------+---------+------+--------+-------------+
| id | select_type | table      | type  | possible_keys | key             | key_len | ref  | rows   | Extra       |

+----+-------------+------------+-------+---------------+-----------------+---------+------+--------+-------------+
|  1 | PRIMARY     | <derived2> | ALL   | NULL          | NULL            | NULL    | NULL |    101 | Using where | 
|  2 | DERIVED     | comment    | index | NULL          | idx_comment_uid | 5       | NULL | 524833 | Using index | 

+----+-------------+------------+-------+---------------+-----------------+---------+------+--------+-------------+
2 rows in set (4.18 sec)
可以看出,MySQL首先是先執行comment_count的檢視定義,將結果儲存在臨時表中(即DERIVED),然後再掃描這一臨時表,選擇出滿足"user_id = 90"的那一條記錄。這樣,雖然我們最終只需要統計90號使用者的評論數,並且comment表的user_id欄位上也有索引,MySQL也會掃描整個comment表,並按user_id分組計算出所有使用者的評論數。一般來說,這鐵定會使你的系統玩完。這裡面還要注意的是即使在進行EXPLAIN時,檢視的物化也是要先執行的,因此若評論很多的話EXPLAIN也是一樣的慢
這個問題的根源是MySQL的查詢優化本來就存在很多問題。對於上述的查詢,要達到比較好的優化效果在資料庫中一般是如下處理的:
1、將對檢視的操作轉化為FROM子句中的子查詢:
select * from (select user_id, count(*) as count from comment group by user_id) as comment_count where user_id = 90;
2、子查詢提升。因為子查詢中使用了group by,因此先將外面的條件作為提升後的having條件
select user_id, count(*) as count from comment group by user_id having user_id = 90;
3、由於having條件中不涉及聚集函式,轉化為where條件
select user_id, count(*) as count from comment where user_id = 90 group by user_id;
4、由於指定where條件後,user_id已經是一個常數,根據常數group by沒意義,因此去掉group by
select user_id, count(*) as count from comment where user_id = 90;
一般從概念上要經過這四步轉化,才能得到最後的優化語句。除第4步無法根據EXPLAIN輸出和查詢效能判斷出MySQL是否進行這一優化外,前3類優化MySQL都不會進行。因此,MySQL要能夠有效的處理上述查詢還有很長的路要走。

PS: 相對來說PostgreSQL的查詢優化能力就強得多,上面的查詢在PostgreSQL中就能夠產生上述優化後的最終執行計劃。PostgreSQL比較關注查詢優化估計與PostgreSQL的學院派風格或PostgreSQL中的rule system有關。