Mysql 表連接的原理

Mysql 表連接的原理

搞後端的肯定要經常接觸到數據庫,搞數據庫一個避免不了的地方就是 join, join的語法很簡單,但是在使用時常常陷入一下兩種誤區:

  • 誤區一: 業務至上,管他三七二十一,再複雜的查詢一個連接語句搞定
  • 誤區二: 敬而遠之,上次寫的慢查詢sql就是使用了join導致的,以後再也不敢用了

先來舉個栗子:

mysql> SELECT * FROM t1;
+------+------+
| m1   | n1   |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
+------+------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM t2;
+------+------+
| m2   | n2   |
+------+------+
|    2 | b    |
|    3 | c    |
|    4 | d    |
+------+------+
3 rows in set (0.00 sec)

現在我們對這張表進行連接:

mysql> SELECT * FROM t1, t2;
+------+------+------+------+
| m1   | n1   | m2   | n2   |
+------+------+------+------+
|    1 | a    |    2 | b    |
|    2 | b    |    2 | b    |
|    3 | c    |    2 | b    |
|    1 | a    |    3 | c    |
|    2 | b    |    3 | c    |
|    3 | c    |    3 | c    |
|    1 | a    |    4 | d    |
|    2 | b    |    4 | d    |
|    3 | c    |    4 | d    |
+------+------+------+------+
9 rows in set (0.00 sec)

這個過程看起來就是把t1表的記錄和t2的記錄連起來組成新的更大的記錄,所以這個查詢過程稱之爲連接查詢。連接查詢的結果集中包含一個表中的每一條記錄與另一個表中的每一條記錄相互匹配的組合,像這樣的結果集就可以稱之爲笛卡爾積。因爲表t1中有3條記錄,表t2中也有3條記錄,所以這兩個表連接之後的笛卡爾積就有3×3=9行記錄。

連接過程簡介

如果我們樂意,我們可以連接任意數量張表,但是如果沒有任何限制條件的話,這些表連接起來產生的笛卡爾積可能是非常巨大的。比方說3個100行記錄的表連接起來產生的笛卡爾積就有100×100×100=1000000行數據!所以在連接的時候過濾掉特定記錄組合是有必要的

下邊我們就要看一下攜帶過濾條件的連接查詢的大致執行過程了,比方說下邊這個查詢語句:

SELECT * FROM t1, t2 WHERE t1.m1 > 1 AND t1.m1 = t2.m2 AND t2.n2 < 'd';

在這個查詢中我們指明瞭這三個過濾條件:

  • t1.m1 > 1
  • t1.m1 = t2.m2
  • t2.n2 < 'd'

那麼這個連接查詢的大致執行過程如下:

  1. 首先確定第一個需要查詢的表,這個表稱之爲驅動表。只需要選取代價最小的那種訪問方法去執行單表查詢語句就好了。此處假設使用t1作爲驅動表,那麼就需要到t1表中找滿足t1.m1 > 1的記錄,因爲表中的數據太少,我們也沒在表上建立二級索引,所以此處查詢t1表的訪問方法就是全表掃描。

  2. 針對上一步驟中從驅動表產生的結果集中的每一條記錄,分別需要到t2表中查找匹配的記錄,所謂匹配的記錄,指的是符合過濾條件的記錄。因爲是根據t1表中的記錄去找t2表中的記錄,所以t2表也可以被稱之爲被驅動表。上一步驟從驅動表中得到了2條記錄,所以需要查詢2次t2表。此時涉及兩個表的列的過濾條件t1.m1 = t2.m2就派上用場了:

    • t1.m1 = 2時,過濾條件t1.m1 = t2.m2就相當於t2.m2 = 2,所以此時t2表相當於有了t2.m2 = 2t2.n2 < 'd'這兩個過濾條件,然後到t2表中執行單表查詢。
    • t1.m1 = 3時,過濾條件t1.m1 = t2.m2就相當於t2.m2 = 3,所以此時t2表相當於有了t2.m2 = 3t2.n2 < 'd'這兩個過濾條件,然後到t2表中執行單表查詢。

從上邊兩個步驟可以看出來,我們上邊嘮叨的這個兩表連接查詢共需要查詢1次t1表,2次t2表。當然這是在特定的過濾條件下的結果,如果我們把t1.m1 > 1這個條件去掉,那麼從t1表中查出的記錄就有3條,就需要查詢3次t2表了。也就是說在兩表連接查詢中,驅動表只需要訪問一次,被驅動表可能被訪問多次。

內連接與外連接

如果驅動表中的記錄即使在被驅動表中沒有匹配的記錄,但我們也仍然需要加入到結果集。爲了解決這個問題,就有了內連接外連接的概念:

  • 對於內連接的兩個表,驅動表中的記錄在被驅動表中找不到匹配的記錄,該記錄不會加入到最後的結果集,我們上邊提到的連接都是所謂的內連接

  • 對於外連接的兩個表,驅動表中的記錄即使在被驅動表中沒有匹配的記錄,也仍然需要加入到結果集。

    MySQL中,根據選取驅動表的不同,外連接仍然可以細分爲2種:

    • 左外連接

      選取左側的表爲驅動表。

    • 右外連接

      選取右側的表爲驅動表。

where 與 on

可是這樣仍然存在問題,即使對於外連接來說,有時候我們也並不想把驅動表的全部記錄都加入到最後的結果集。這就犯難了,有時候匹配失敗要加入結果集,有時候又不要加入結果集,這咋辦,有點兒愁啊。。。噫,把過濾條件分爲兩種不就解決了這個問題了麼,所以放在不同地方的過濾條件是有不同語義的:

  • WHERE子句中的過濾條件

    WHERE子句中的過濾條件就是我們平時見的那種,不論是內連接還是外連接,凡是不符合WHERE子句中的過濾條件的記錄都不會被加入最後的結果集。

  • ON子句中的過濾條件

    對於外連接的驅動表的記錄來說,如果無法在被驅動表中找到匹配ON子句中的過濾條件的記錄,那麼該記錄仍然會被加入到結果集中,對應的被驅動表記錄的各個字段使用NULL值填充。

    需要注意的是,這個ON子句是專門爲外連接驅動表中的記錄在被驅動表找不到匹配記錄時應不應該把該記錄加入結果集這個場景下提出的,所以如果把ON子句放到內連接中,MySQL會把它和WHERE子句一樣對待,也就是說:內連接中的WHERE子句和ON子句是等價的。

一般情況下,我們都把只涉及單表的過濾條件放到WHERE子句中,把涉及兩表的過濾條件都放到ON子句中,我們也一般把放到ON子句中的過濾條件也稱之爲連接條件

左外連接和右外連接簡稱左連接和右連接,所以下邊提到的左外連接和右外連接中的字都用括號擴起來,以表示這個字兒可有可無。

我們前邊說過,連接的本質就是把各個連接表中的記錄都取出來依次匹配的組合加入結果集並返回給用戶。不論哪個表作爲驅動表,兩表連接產生的笛卡爾積肯定是一樣的。而對於內連接來說,由於凡是不符合ON子句或WHERE子句中的條件的記錄都會被過濾掉,其實也就相當於從兩表連接的笛卡爾積中把不符合過濾條件的記錄給踢出去,所以對於內連接來說,驅動表和被驅動表是可以互換的,並不會影響最後的查詢結果。但是對於外連接來說,由於驅動表中的記錄即使在被驅動表中找不到符合ON子句條件的記錄時也要將其加入到結果集,所以此時驅動表和被驅動表的關係就很重要了,也就是說左外連接和右外連接的驅動表和被驅動表不能輕易互換。

發佈了265 篇原創文章 · 獲贊 361 · 訪問量 79萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章