MySQL中的視圖及性能問題

視圖是MySQL 5.0中增加的三大新功能之一(另外兩個是存儲過程與觸發器),也是一般稍微“高級”一點的數據庫所必需要有的功能。MySQL在定義視圖上沒什麼限制,基本上所有的查詢都可定義爲視圖,並且也支持可更新視圖(當然只有在視圖和行列與基礎表的行列之間存在一一對應關係時才能更新),因此從功能上說MySQL的視圖功能已經很完善了。

然而若要在應用中使用視圖,還需要了解處理視圖時的性能,而MySQL在這方面問題是比較大的,需要特別注意。首先要知道MySQL在處理視圖時有兩種算法,分別稱爲MERGE和TEMPTABLE。在執行"CREATE VIEW"語句時可以指定使用哪種算法。所謂MERGE是指在處理涉及到視圖的操作時,將對視圖的操作根據視圖的定義進行展開,有點類似於C語言中的宏展開。比如設有以下的表(類似於博客中的評論):
CREATE TABLE `comment` (
  `id` int(11) NOT NULL,
  `user_id` int(11) default NULL,
  `content` varchar(255) default NULL,
  PRIMARY KEY  (`id`),
  KEY `idx_comment_uid` (`user_id`)
) ENGINE=InnoDB;
假設user_id < 10000的用戶爲VIP用戶,我們可以這樣創建一個視圖來表示VIP用戶的評論:
CREATE VIEW vip_comment AS SELECT * FROM comment WHERE user_id < 10000;
這時我們在操作vip_comment視圖時使用的就是MERGE算法。如:
mysql > EXPLAIN EXTENDED SELECT count(*) FROM vip_comment WHERE user_id < 0;
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+--------------------------+
| id | select_type | table   | type  | possible_keys   | key             | key_len | ref  | rows | Extra                    |
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | comment | range | idx_comment_uid | idx_comment_uid | 5       | NULL |   10 | Using where; Using index |
+----+-------------+---------+-------+-----------------+-----------------+---------+------+------+--------------------------+
mysql> show warnings;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                               |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | select count(0) AS `count(*)` from `test`.`comment` where ((`test`.`comment`.`user_id` < 0) and (`test`.`comment`.`user_id` < 10000)) | 
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------+
可以看到,對vip_comment的操作已經被擴展爲對comment表的操作。

一般來說在能夠使用MERGE算法的時候MySQL處理視圖上沒什麼性能問題,但並非在任何時候都能使用MERGE算法。事實上,只要視圖的定義稍稍有點複雜,MySQL就沒辦法使用MERGE算法了。準確的說,只要視圖定義中使用了以下SQL構造塊就無法使用MERGE算法:
  • 聚集函數
  • DISTINCT
  • GROUP BY
  • HAVING
  • 集合操作(在MySQL中只有UNION, UNION ALL,沒有EXCEPT和INTERSECT)
  • 子查詢
確實,在視圖定義比較複雜的情況下,要對視圖操作進行有效的優化是非常困難的。因此在這個時候,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有關。


轉自 :http://wangyuanzju.blog.163.com/blog/static/130292007714102859807/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章