然而若要在應用中使用視圖,還需要了解處理視圖時的性能,而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)
- 子查詢
比如我們希望使用如下的視圖來表示每個用戶的評論數,即:
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/