010 --MySQL查詢優化器的局限性
MySQL的萬能"嵌套循環"並不是對每種查詢都是最優的。不過還好,mysql查詢優化器只對
少部分查詢不適用,而且我們往往可以通過改寫查詢讓mysql高效的完成工作。
在這我們先來看看mysql優化器有哪些局限性:
1.關聯子查詢
mysql的子查詢實現得非常糟糕。最糟糕得一類查詢是where條件中包含in()的子查詢語句。
例如,我們希望找到sakila數據庫中,演員Penlope Guiness參演的所有影片信息。
很自然的,我們會按照下面的方式用子查詢實現:
select * from sakila.film where film_id in ( select film_id from sakila.film_actor where actor_id = 1 )
你很容易認為mysql應該由內而外的去執行這個查詢,通過子查詢中的條件先找出所匹配的
film_id。所以你看你會認為這個查詢可能會是這樣:
-- SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id = 1; -- Result: 1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980 SELECT * FROM sakila.film WHERE film_id IN(1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980);
不幸的是,事實恰恰相反。MYSQL想通過外部的關聯條件用來快速的篩選子查詢,它可能認為
這會讓子查詢更效率。mysql會這樣重寫查詢:
SELECT * FROM sakila.film WHERE EXISTS ( SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id);
這樣的話,子查詢將會依賴外部表的數據,而不會被優先執行。
mysql將會全表掃描film表,然後循環執行子查詢。在外表很小的情況下,
不會有什麽問題,但在外表很大的情況下,性能將會非常差。幸運的是,
很容易用關聯查詢來重寫。
mysql> SELECT film.* FROM sakila.film -> INNER JOIN sakila.film_actor USING(film_id) -> WHERE actor_id = 1;
其他的好的優化方法是用group_concat手工生成in()的列表。有時甚至會比JOIN查詢
更快。總之,雖然in()子查詢在很多情況下工作不佳,但exist()或者其他等價的子查詢
有時也工作的不錯。
關聯子查詢性能並不是一直都很差的。
子查詢 VS 關聯查詢
--關聯子查詢
mysql> explain select film_id, language_id from sakila.film where not exsits ( select * from sakila.film_actor where film_actor.film_id = film.film_id )
********************* 1. row ***********************************
id : 1
select_type: PRIMARY
table: film
type: all
possible_keys: null
key: null
key_len: null
ref: null
rows: 951
Extra: Using where
********************* 2. row ***********************************
id : 2
select_type: Dependent subquery
table: film_actor
type: ref
possible_keys: idx_fx_film_id
key: idx_fx_film_id
key_len: 2
ref: film.film_id
rows: 2
Extra: Using where;Using index
--關聯查詢
mysql> explain select film.film_id, film.language_id from sakila.film
left outer join sakila.film_actor using(film_id) where film_actor.film_id is null
********************* 1. row ***********************************
id : 1
select_type: simple
table: film
type: all
possible_keys: null
key: null
key_len: null
ref: null
rows: 951
Extra:
********************* 2. row ***********************************
id : 1
select_type: simple
table: film_actor
type: ref
possible_keys: idx_fx_film_id
key: idx_fx_film_id
key_len: 2
ref: sakila.film.film_id
rows: 2
Extra: Using where;Using index;not exists;
可以看到,這裏的執行計劃幾乎一樣,下面是一些細微的差別:
1. 表 film_actor的訪問類型一個是Dependent subquery 另一是simple,這對底層存儲引擎接口來說,沒有任何不同;
2. 對 film表 第二個查詢沒有using where,但這不重要。using子句和where子句實際上是完全一樣的。
3. 第二個表film_actor的執行計劃的Extra 有 "Not exists" 這是我們先前提到的提前終止算法,mysql通過not exits優化
來避免在表film_actor的索引中讀取任何額外的行。這完全等效於直接使用 not exist ,這個在執行計劃中也一樣,一旦匹配到一行
數據,就立刻停止掃描
測試結果為:
查詢 每秒查詢數結果(QRS)
NOT EXISTS 子查詢 360
LEFT OUTER JOIN 425
這裏顯示使用子查詢會略慢些。
另一個例子:
不過每個具體地案例會各有不同,有時候子查詢寫法也會快些。例如,當返回結果只有一個表的某些列的時候。
聽起來,這種情況對於關聯查詢效率也會很好。具體情況具體分析,例如下面的關聯,我們希望返回所有包含同一個演員參演的電影
因為電影會有很多演員參演,所以可能返回一些重復的記錄。
mysql-> select film.film_id from sakila.film inner join sakila.film_actor using (film_id)
我們需要用distinct 和 group by 來移除重復的記錄
mysql-> select distinct film.film_id from sakila.film inner join sakila.film_actor using (film_id)
但是,回頭看看這個查詢,到底這個查詢返回的結果意義是什麽?至少這樣的寫法會讓sql的意義不明顯。
如果是有exists 則很容易表達"包含同一個參演演員"的邏輯。而且不需要使用 distinct 和 Group by,也不會有重復的結果集。
我們知道一旦使用了 distinct 和 group by 那麽在查詢的執行過程中,通常需要產生臨時中間表。
mysql -> select film_id from sakila.film_actor where exists(select * from sakila.film_actor where film.film_id = film_actor.film_id)
測試結果為:
查詢 每秒查詢數結果(QRS)
INNER JOIN 185
EXISTS 子查詢 325
這裏顯示使用子查詢會略快些。
通過上面這個詳細的案例,主要想說明兩點:
一是不需要聽取哪些關於子查詢的 "絕對真理",(即別用使用子查詢)
二是應該用測試來驗證子查詢的執行疾患和響應時間的假設。
2.union的限制
有時,mysql無法將限制條件從外層"下推"到內層,這使得一些可以限制結果集和附加的優化都無法運行。
如果你想任何單獨的查詢都可以從一個limit獲益,
或者你想order by也是基於所有子查詢一次結合,
則你需要在每個子查詢加上相應的子語句。
例如:
(SELECT first_name, last_name FROM sakila.actor ORDER BY last_name) UNION ALL (SELECT first_name, last_name FROM sakila.customer ORDER BY last_name) LIMIT 20;
這個查詢將會保存200行從actor查出來的數據和customer表的599行數據,
然後放入一個臨時表,然後選取靠前的20條數據。
你可以通過在每個查詢都加上limit 20 來預防這個情況。
如下:
(SELECT first_name, last_name FROM sakila.actor ORDER BY last_name LIMIT 20) UNION ALL (SELECT first_name, last_name FROM sakila.customer ORDER BY last_name LIMIT 20) LIMIT 20;
這樣只會查出40條數據了,大大提升了查詢效率。
3.索引合並優化
4.等值傳遞
有時候等值傳遞也會造成很大的性能消耗。
5.並行執行
mysql不能並行執行一個單獨的查詢在不同的cpu.可能其他數據庫會提供這個特性,但mysql沒有提供。
我們提及這個就是希望你們不要花時間去弄怎麽在mysql配置並行查詢。
6.索引關聯
mysql並不是完全支持哈希關聯,大部分關聯都是嵌套循環關聯。
7.松散索引掃描
由於歷史原因,mysql不支持松散索引掃描
8.最大值和最小值優化
對於min()和max(),mysql優化做的並不好
9.在同一個表查詢和更新
下面是個無法運行的sql,雖然這是一個符合標準的sql語句。
這個sql語句嘗試將兩個表中相似行的數量記錄到字段cnt中:
mysql> UPDATE tbl AS outer_tbl -> SET cnt = ( -> SELECT count(*) FROM tbl AS inner_tbl -> WHERE inner_tbl.type = outer_tbl.type -> );
ERROR 1093 (HY000): You can’t specify target table ‘outer_tbl‘ for update in FROM
clause
可以通過生成表的形式來繞過上面的限制,因為mysql只會把這個表當做一個臨時表處理。實際上,
這執行了兩個查詢:一個是子查詢的select語句,另一個是多表關聯update,只是關聯的表是一個臨時表。
子查詢會在update語句打開表之前就完成,所以下面的查詢將正常運行。
mysql> UPDATE tbl -> INNER JOIN( -> SELECT type, count(*) AS cnt -> FROM tbl -> GROUP BY type -> ) AS der USING(type) -> SET tbl.cnt = der.cnt;
010 --MySQL查詢優化器的局限性