1. 程式人生 > >MySQL SQL 多個Join on(表連線) 和Where間的執行順序(nest loop join機制)【轉】

MySQL SQL 多個Join on(表連線) 和Where間的執行順序(nest loop join機制)【轉】

版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/qq_27529917/article/details/78447882

在mysql中,多表連線採用nest loop join,即迴圈巢狀連線的方式,其他還有歸併排序連線,雜湊連線;

mysql sql優化器會對sql的表的連線順序做一定的優化,並不見得一定是我們寫的sql的表連線順序,會盡量使用查詢結果集最小的表作為驅動表,前提是連線順序改變不會改變查詢結果,然後按照優化後的順序和其他的表逐漸連線查詢。 也就是說left join連線並不一定是從左邊關聯到右邊,也有可能是從右邊關聯到左邊,left join僅僅是保證左側表符合條件的記錄會進入到結果集中,具體順序使用explain執行檢視。

先分析內連線或者是多表關聯查詢的情況:,假設sql是這樣的:

select a.id,b.name,c.adress 
from A a 
    inner join B b on a.bid = b.id 
    inner join C c on a.cid = c.id 
    inner join D d on a.did = d.id
where a.id in (1,23456) and b.class = '1001' and c.id > 15 and d.age < 30;
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
select
a.id,b.name,c.adress from A a ,B b ,C c ,D d where a.bid = b.id and c on a.cid = c.id and d on a.did = d.id and a.id in (1,23456) and c.id > 15 and d.age < 30;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如何確定A,B,C,D之間的連線順序?如何確定驅動表?MySQL會優先選用結果集最小的那張表作為驅動表來和其他的表連線。如何得到結果集最小的表? MySQL會估算每一個表的結果集大小,如何估算? 會使用WHERE中的針對各個表的查詢條件來估算每個表的結果集大小,此時索引就發揮了作用。

表A的條件是a.id in (1,2,3,4,5,6),假設id是唯一鍵,則A的結果集是6;

表B的條件是b.class = ‘1001’,若class上有索引,則使用此索引估算,假設B的總記錄數是100,class索引的分佈率是10%,即class有10個不同的值,則估算為100*10% = 10,即B的結果集為10;

表C的條件是 c.id > 15,假設C的總記錄數是20,id是唯一鍵,則C的結果集最多為5;估算D的結果集方法類似,D的結果集為12;

若WHERE中不含指定表的查詢條件或者查詢的條件不包含索引,則此表的結果集預設為記錄數最大值,但並不代表此表的結果集大於其他的表,因為有些表本身就沒幾條資料。

如此就確定了表的結果集大小,C , A , B , D。當然結果集大小是決定表連線順序的一個關鍵因素,SQL優化器在做物理查詢優化時不僅僅會考慮結果集,還會考慮查詢所需的資料是否在快取中,如果不在從磁碟讀取的代價,讀取到資料後將二進位制資料解析成資料行的代價,以及表與表連線可能的結果集大小等等因素,綜合各方面來確定表的連線順序。

假設sql執行器按照C , A , B , D的順序來連線,先根據where內C表的條件查詢C表,得到一個結果集。然後關聯查詢A表,在關聯時以ON 內a.cid = c.id 為關聯條件。若關聯的C表的欄位不是唯一的話,可能生成一對多的關聯,即一行A表記錄生成多條結果集的記錄。以on條件關聯後,在執行where內關於C表的查詢條件c.id > 15;那麼c.id > 15這個查詢條件可以放在on之後,也可以放在where之後,on之後是在生成臨時表之前過濾C表的記錄,而WHERE之後是對臨時表的記錄做過濾,理論上放在on之後效率會高一點,但where的條件若有索引則會使用索引,所以效率也不算低。而且若on之後跟的條件不是針對C表的話,可能會對結果集產生影響。AC結果集的臨時表再去關聯B表,一樣先on關聯,然後where過濾,依次關聯和過濾直到表關聯結束。

當然以上的sql都是a.bid = b.id and a.cid = c.id,關聯條件均在A表上,有些時候時間接關聯,比如a.bid = b.id and b.cid = c.id,此時表的關聯順序可能是A > B > C,或者 C > B >A,或者先B > C,然後關聯A等等。SQL優化器會確保無論如何調整關聯順序不會對結果集有影響。

以上均是內連線時的情況,外連線要稍微複雜點。

假設SQL如下:

select a.id,b.name,c.adress 
from A a 
    left join B b on a.bid = b.id 
    left join C c on b.cid = c.id 
    right join D d on d.age = c.dage
where a.id in (1,23456) and b.class = '1001' and c.id > 15 and d.age < 30;
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

對於外連線,有一個主表的概念,即left的左邊表或者right的右邊表,對於主表,會返回主表所有符合條件的記錄行,而對於副表,則只返回能和主表關聯的行,一條主表記錄根據on條件和副表內每一行匹配,有多少行匹配上了就生成多少臨時表的記錄,也就是說主表的一行記錄可能生成臨時表的多條記錄,若副表中沒有匹配行時,則副表生成一條所有欄位均為null的記錄和主表的記錄行匹配, 確保主表的符合條件的行能進入臨時表。

對於外連線,大部分情況會以我們的SQL順序來執行,因為有主表的結果集限制,上述SQL一般會先根據WHERE條件從A表重 查詢出符合條件的記錄行,作為主表和B表以ON條件關聯,A結果集中的每條記錄均和B表中符合條件的每條記錄行生成AB臨時表的一條記錄,若B表有多個符合記錄行,則生成多個臨時錶行,若B表沒有符合條件的行,則生成一條所有欄位均為null的行與A的記錄行連線,若沒有ON條件,則以“笛卡爾積”的形式連線,即A結果集的每一行和B表的每一行均連線生成臨時表的記錄。

當ON執行完之後,同樣用WHERE條件過濾臨時結果集中不符合條件的記錄行,和內連線的機制相同,之後再次關聯其他表。最後right join D,此時D表時主動表,D表關聯A,B,C查詢後的臨時表,最終會返回D表中所有符合條件的記錄行。

以上就是nest loop join機制,巢狀迴圈連線。一層一層的連線,迴圈用外層結果集的記錄行和內層的所有符合條件ON條件的記錄依次連線,內層沒有符合條件的生成所有欄位為null的記錄行,當不存在ON條件是以“笛卡爾積”的形式連線。連線過後where過濾,再連線,在過濾,直到左右表均連線完畢。連線完畢後有group by字句則執行分組,有having字句的則對分組後的結果集再過濾,所以having執行在where之後,因此有些條件放where字句內能縮小分組前的結果集,提高執行效率。之後還有order by字句的則執行排序,最後得到查詢的結果。

感謝原作者的詳細分析!這裡我對最後一個外連線的例子說說自己的看法:
巢狀迴圈連線,就是先根據Join的型別和on條件進行逐層連線,每次連線完後用where對結果進行過濾。 在這個例子裡,第一步只有A一個表,所以沒有join的操作,直接對A用where a.id in (1,2,3,4,5,6)進行過濾,符合條件的記錄作為主表,得到臨時表t_A;第二步,t_A和B按照 A left join B b on a.bid = b.id 的規則來連線,對結果b.class = '1001’進行過濾,得到臨時表t_AB;第三步, t_AB left join C c on b.cid = c.id ,對結果用 where c.id > 15進行過濾,得到新的臨時表 t_ABC;第四步, t_ABC right join D d on d.age = c.dage,對連線結果用 where d.age < 30 進行過濾,得到最終結果 t_ABCD。

在mysql中,多表連線採用nest loop join,即迴圈巢狀連線的方式,其他還有歸併排序連線,雜湊連線;