MySQL中“關聯”一次所包含的意義比一般意義上了解的要更廣泛。總的來說,MySQL認為任何一個查詢都是一次“關聯”——并不僅僅是一個查詢需要到兩個表比對才叫關聯,是以在MySQL中,每一個查詢,每一個片段(包括子查詢,甚至基于單表的SELECT)都可能是關聯。
是以了解MySQL如何執行關聯查詢至關重要。我們先來看一個UNION查詢的例子。對于UNION查詢,MySQL先将一系列的單個查詢結果放到一個臨時表中,然後再重新讀出臨時表資料來完成UNION查詢。在MySQL的概念中,每個查詢都是一次關聯,是以讀取結果臨時表時也是一次關聯。
目前MySQL關聯執行的政策很簡單:MySQL對任何關聯都執行嵌套循環關聯操作,即MySQL現在一個表中循環取出單條資料,然後再嵌套循環到下一個表中尋找比對的行,依次下去,直到找到所有表中比對的行為止。然後根據各個表比對的行,傳回查詢中需要的各個列。
按照這樣的方式查找第一個表記錄,再嵌套查詢下一個關聯表,然後回溯到上一個表,在MySQL中是通過嵌套循環的方式實作——正如其名“嵌套循環關聯”。請看下面的例子中的簡單查詢:
mysql>select tbl1.col1,tbl2.col2
->from tbl1 inner join tbl2 using(col3)
->where tbl1.col1 in(5,6);
假設MysQL按照查詢中的表順序進行關聯操作,我們則可以用下面的僞代碼表示MySQL将如何完成這個查詢:
List tbl1 = tbl中col1為5,6的行;
for(tbrow : tb1){
List tb2 = (tbl2中col3=tbrow中col3的行)
for(tb2row : tb2){
輸出【tbl1.col1,tbl2.col2】
}
}
上面的查詢對于單表和多變關聯查詢都适用,如果是一個單表查詢,那麼隻需完成上面外層的基本操作。對于外連接配接上面的執行過程仍然适用。例如,我們将上面查詢修改如下:
mysql>select tbl1.col1,tbl2.col2
->from tbl1 left join tbl2 using(col3)
->where tbl1.col1 in(5,6)
對應的僞代碼如下,我們用黑體标示出不同的部分:
List tbl1 = tbl中col1為5,6的行;
for(tb1row : tb1){
List tb2 = (tbl2中col3=tb1row中col3的行)
for(tb2row : tb2){
if(tbl1row.col3== tbl12row.col3) {
輸出[tbl1.col1, tbl2.col2]
}else{
輸出【tbl1.col1,null】
}
}
}
從本質上說,MySQL對所有的類型的查詢都以同樣的方式運作。例如,MySQL在FROM子句中遇到子查詢時,先執行子查詢并将其結果放到一個臨時表中,然後将這個臨時表當作一個普通表對待(正如其名“派生表”)。MySQL在執行UNION查詢時也使用類似的臨時表,在遇到又外連接配接的時候MySQL将其改為等價的左外連接配接。簡而言之MysQL會将所有的查詢類型都轉換成類似的執行計劃。
執行計劃
和很多其他關系資料庫不同,MySQL并不會生成查詢位元組碼來執行查詢。MySQL生成查詢的一棵指令樹,然後通過存儲引擎執行完成這顆指令樹并傳回結果。最終的執行計劃包含了重構查詢的全部資訊。如果對某個查詢執行explain extended後,在執行show warnings,就可以看到重構出的查詢。
任何多表查詢都可以用一棵樹表示,mysql總是從一個表開始一直嵌套循環,回溯完成所有表關聯。是以,MySQL的執行計劃總是如下圖所示:,這是一棵左側深度優先的樹。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN0LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9smeNl3YU1EeBRVTz8GSiZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TNycjNyUDMyIDOxUDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
關聯查詢中,任何的子查詢都可以被改編成左連接配接或者内連接配接,但他們的查詢效率因為查詢執行器的關系可能會有所不同,例如下面這句: mysql>select * from sakila.film ->where film_id in(select film_id from sakila.film_actor where actor_id = 1); 我們可能以為它會先執行in語句裡面的句子,再做外部關聯,實際上,查詢執行器,因為film_id要關聯外部表,是以我們可以通過(explain extended來看這個查詢被改成了什麼樣子): select * from sakila.film where exists(selct * from sakila.film_actor where actor_id =1 and film_actor.film_id = film.film_id);
通過explain我們可以看到,子查詢是一個相關子查詢 ---------------------------------------------------------------------------------------------------------------------------- id select_type table type possible_keys 1 primary film ALl null 2 DEPENDENT SUBQUERY film_actor eq_ref primary,idx_fk_film_id
根據explain我們可以看到,MySQL先選擇對file表進行全表掃描,然後根據傳回的film_id逐個執行子查詢。這個時候我們可以通過重寫這個查詢: mysql>select film.* from sakila.film ->inner join sakila.film_actor using(film_id) ->where actor_id = 1;
但也并不是所有的左連接配接或者内連結查詢都比子查詢要快,我們這時候就要記住一條準則:通過explain檢視執行具體細節,通過explain extended檢視查詢執行器改寫後的語句,執行show warnings會将上句查詢的sql優化後的語句查詢出來。
上面的查詢對于單表和多變關聯查詢都适用,如果是一個單表查詢,那麼隻需完成上面外層的基本操作。對于外連接配接上面的執行過程仍然适用。