從本質上說,mysql對所有的類型的查詢都以同樣的方式運作,如:mysql在from子句中遇到子查詢,先執行子查詢并将結果放到一個臨時表中,然後将這個臨時表當作一個普通表對待(正如其名:派生表),mysql在執行union查詢時也使用類似的臨時表,在遇到右外連接配接是,mysql将其改寫成等價的左外連接配接,換而言之,目前版本的mysql會将所有的查詢類型都換換成類似的執行計劃,不過,不是所有的查詢都可以轉換成上面的形式,如:全外連接配接就無法通過嵌套循環和回溯的方式完成,這也是mysql并不支援全外連接配接的原因。
注意:mysql臨時表沒有任何索引,在編寫複雜的子查詢和關聯查詢的時候需要注意這一點,這一點對union查詢也一樣,在mysql5.6和mariadb中有了重大改變,這兩個版本都引入了更加複雜的執行計劃。
執行計劃:
和很多其他關系資料庫不同,mysql并不會生成查詢位元組碼來執行查詢,mysql生成查詢的一棵指令樹,然後通過存儲引擎執行完成這棵指令樹并傳回結果,最終的執行計劃包含了重構查詢的全部資訊,如對某個查詢執行explain extended後,再執行show warnings,就可以檢視到重構的查詢。mysql總是從一個表開始一直嵌套循環,回溯完成所有表的關聯。是以,mysql的執行計劃是一棵左側深度優先的樹,然後回溯到上一層關聯:
關聯查詢優化器:
mysql優化器最重要的一部分就是關聯查詢優化,它決定了多個表關聯時的順序,通常多表關聯的時候,可以有多種不同的關聯順序來獲得相同的執行效果,關聯查詢優化器則通過評估不同順序時的成本來選擇一個代價最小的關聯順序。
示例:
explain select film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from sakila.film join sakila.film_actor using(film_id) join sakila.actor using(actor_id)\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: actor
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 200
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: film_actor
type: ref
possible_keys: PRIMARY,idx_fk_film_id
key: PRIMARY
key_len: 2
ref: sakila.actor.actor_id
rows: 13
Extra: Using index
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: film
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 2
ref: sakila.film_actor.film_id
rows: 1
Extra: NULL
3 rows in set (0.00 sec)
執行計劃中可以看到,mysql從actor表開始,使用film_actor表的索引film_id來查找對應的actor_id值,然後根據film表的主鍵找到對應的記錄,oracle使用者會用下面的術語描述:actor表作為驅動表先找到file_actor表,然後以此結果為驅動表再查找film表。
下面使用straight_join關鍵字指定關聯表順序,如下:
explain select straight_join film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from sakila.film join sakila.film_actor using(film_id) join sakila.actor using(actor_id)\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 1000
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: film_actor
type: ref
possible_keys: PRIMARY,idx_fk_film_id
key: idx_fk_film_id
key_len: 2
ref: sakila.film.film_id
rows: 2
Extra: Using index
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: actor
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 2
ref: sakila.film_actor.actor_id
rows: 1
Extra: NULL
3 rows in set (0.00 sec)
從上面的explain結果上看,mysql自動選擇的關聯順序與使用straight_join關鍵字的關聯順序完全倒轉過來,從上面的對比結果上可以看出,mysql把關聯表倒轉過來後,film隻掃描一行,而film在最前時掃描行數是1000.
如果mysql選擇首先掃描actor表,隻會傳回200條記錄進行後面的嵌套循環查詢,即,倒轉關聯順序會讓查詢進行更少的嵌套循環和回溯操作,為了驗證優化器的選擇是否正确,我們單獨執行了這兩個查詢,并且看last_query_cost狀态值(這裡我使用的虛拟機做測試),結果mysql選擇的順序預估成本為3883.340463,而我們手工指定的順序預估成本為4012.731064。
這個簡單的示例說明mysql是如何選項合适的關聯順序來讓查詢執行的成本盡可能低,重新定義關聯的順序是優化器非常重要的一部分,不過有時候,優化器給出的并不是最優的關聯順序,這時可以使 用straight_join關鍵字重寫查詢,讓優化器按照你認為的最優的關聯順序執行,不過,人的判斷很難那麼精準,是以大部分時候,優化器做出的選擇比普通人判斷的要更精準。
關聯優化器會嘗試在所有的關聯順序中選擇一個成本最小的來生成執行計劃樹,如果可能,優化器會周遊每一個表後逐個做嵌套循環計算每一顆可能的執行計劃樹的成本,最後傳回一個最優的執行計劃。
但是,糟糕的是,如果有超過n個表關聯,那麼需要檢查n的階乘種關聯順序,我們稱之為所有可能的執行計劃的“搜尋空間”,搜尋空間的增長速度非常快,如:若是10個關聯,那麼共有3628800種關聯順序,當搜尋空間非常大的時候,優化器不可能逐一評估每一種關聯順序的成本,這時,優化器選擇使用貪婪搜尋的方式查找最優的關聯順序,實際上,當需要關聯的表超過optimizer_search_depth的限制的時候,就會選擇貪婪搜尋模式了,optimizer_search_depth這個參數可以根據需要動态指定大小,預設為62。