top
Loading...
7.2.10.MySQL如何優化嵌套Join
7.2.10. MySQL如何優化嵌套Join

表示聯接的語法允許嵌套聯接。下面的討論引用了13.2.7.1節,“JOIN語法”中描述的聯接語法。

SQL標準比較,table_factor語法已經擴展了。后者只接受table_reference,而不是括號內所列的。

table_reference項列表內的每個逗號等價于內部聯接,這是一個保留擴展名。例如:

SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
                 ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)

等價于:

SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
                 ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)

MySQL中,CROSS JOIN語法上等價于INNER JOIN (它們可以彼此代替。在標準SQL中,它們不等價。INNER JOIN結合ON子句使用;CROSS JOIN 用于其它地方。

總的來說,在只包含內部聯接操作的聯接表達式中可以忽略括號。刪除括號并將操作組合到左側后,聯接表達式:

t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
   ON t1.a=t2.a

轉換為表達式:

(t1 LEFT JOIN t2 ON t1.a=t2.a) LEFT JOIN t3
    ON t2.b=t3.b OR t2.b IS NULL

但是這兩個表達式不等效。要說明這點,假定表t1t2t3有下面的狀態:

·         t1包含行{1}{2}

·         t2包含行{1,101}

·         t3包含行{101}

在這種情況下,第1個表達式返回包括行{1,1,101,101}{2,NULL,NULL,NULL}的結果,第2個表達式返回行{1,1,101,101}{2,NULL,NULL,101}

mysql> SELECT *
    -> FROM t1
    ->      LEFT JOIN
    ->      (t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
    ->      ON t1.a=t2.a;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL | NULL |
+------+------+------+------+
 
mysql> SELECT *
    -> FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
    ->      LEFT JOIN t3
    ->      ON t2.b=t3.b OR t2.b IS NULL;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL |  101 |
+------+------+------+------+

在下面的例子中,外面的聯接操作結合內部聯接操作使用:

t1 LEFT JOIN (t2t3) ON t1.a=t2.a

該表達式不能轉換為下面的表達式:

t1 LEFT JOIN t2 ON t1.a=t2.at3.

對于給定的表狀態,第1個表達式返回行{1,1,101,101}{2,NULL,NULL,NULL},第2個表達式返回行{1,1,101,101}{2,NULL,NULL,101}

mysql> SELECT *
    -> FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL | NULL |
+------+------+------+------+
 
mysql> SELECT *
    -> FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+------+------+------+------+
| a    | a    | b    | b    |
+------+------+------+------+
|    1 |    1 |  101 |  101 |
|    2 | NULL | NULL |  101 |
+------+------+------+------+

因此,如果我們忽略聯接表達式中的括號連同外面的聯接操作符,我們會改變原表達式的結果。

更確切地說,我們不能忽視左外聯接操作的右操作數和右聯接操作的左操作數中的括號。換句話說,我們不能忽視外聯接操作中的內表達式中的括號。可以忽視其它操作數中的括號(外部表的操作數)

對于任何表t1t2t3和屬性t2.bt3.b的任何條件P,下面的表達式:

(t1,t2) LEFT JOIN t3 ON P(t2.b,t3.b)

等價于表達式

t1t2 LEFT JOIN t3 ON P(t2.b,t3.b)

如果聯接表達式(join_table)中的聯接操作的執行順序不是從左到右,我們則應討論嵌套的聯接。這樣,下面的查詢:

SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a
  WHERE t1.a > 1
 
SELECT * FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a
  WHERE (t2.b=t3.b OR t2.b IS NULL) AND t1.a > 1

聯接表:

t2 LEFT JOIN t3 ON t2.b=t3.b
t2, t3

認為是嵌套的。第1個查詢結合左聯接操作則形成嵌套的聯接,而在第二個查詢中結合內聯接操作形成嵌套聯接。

在第1個查詢中,括號可以忽略:聯接表達式的語法結構與聯接操作的執行順序相同。但對于第2個查詢,括號不能省略,盡管如果沒有括號,這里的聯接表達式解釋不清楚。(在外部擴展語法中,需要第2個查詢的(t2t3)的括號,盡管從理論上對查詢分析時不需要括號:這些查詢的語法結構將仍然不清楚,因為LEFT JOINON將充當表達式(t2,t3)的左、右界定符的角色)

前面的例子說明了這些點:

·         對于只包含內聯接(而非外聯接)的聯接表達式,可以刪除括號。你可以移除括號并從左到右評估(或實際上,你可以按任何順序評估表)

·         總的來說,對外聯接卻不是這樣。去除括號可能會更改結果。

·         總的來說,對外聯接和內聯接的結合,也不是這樣。去除括號可能會更改結果。

含嵌套外聯接的查詢按含內聯接的查詢的相同的管道方式執行。更確切地說,利用了嵌套環聯接算法。讓我們回憶嵌套環聯接執行查詢時采用什么算法。

假定我們有一個如下形式的表T1T2T3的聯接查詢:

SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2)
                 INNER JOIN T3 ON P2(T2,T3)
  WHERE P(T1,T2,T3).

這里,P1(T1,T2)P2(T3,T3)是一些聯接條件(表達式),其中P(t1,t2,t3)是表T1T2T3的列的一個條件。

嵌套環聯接算法將按下面的方式執行該查詢:

 

FOR each row t1 in T1 {
  FOR each row t2 in T2 such that P1(t1,t2) {
    FOR each row t3 in T3 such that P2(t2,t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3; OUTPUT t;
      }
    }
  }
}

符號t1||t2||t3表示“連接行t1t2t3的列組成的行”。在下面的一些例子中,出現行名的NULL表示NULL用于行的每個列。例如,t1||t2||NULL表示“連接行t1t2的列以及t3的每個列的NULL組成的行”。

現在讓我們考慮帶嵌套的外聯接的查詢:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON P2(T2,T3))
              ON P1(T1,T2)
  WHERE P(T1,T2,T3)

對于該查詢我們修改嵌套環模式可以得到:

FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) {
    BOOL f2:=FALSE;
    FOR each row t3 in T3 such that P2(t2,t3) {
      IF P(t1,t2,t3) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f2=TRUE;
      f1=TRUE;
    }
    IF (!f2) {
      IF P(t1,t2,NULL) {
        t:=t1||t2||NULL; OUTPUT t;
      }
      f1=TRUE;
    }
  }
  IF (!f1) {
    IF P(t1,NULL,NULL) {
      t:=t1||NULL||NULL; OUTPUT t;
    }
  }
}
 

總的來說,對于外聯接操作中的第一個內表的嵌套環,引入了一個標志,在環之前關閉并且在環之后打開。如果對于外部表的當前行,如果匹配表示內操作數的表,則標志打開。如果在循環結尾處標志仍然關閉,則對于外部表的當前行,沒有發現匹配。在這種情況下,對于內表的列,應使用NULL值補充行。結果行被傳遞到輸出進行最終檢查或傳遞到下一個嵌套環,但只能在行滿足所有嵌入式外聯接的聯接條件時。

在我們的例子中,嵌入了下面表達式表示的外聯接表:

(T2 LEFT JOIN T3 ON P2(T2,T3))

請注意對于有內聯接的查詢,優化器可以選擇不同的嵌套環順序,例如:

FOR each row t3 in T3 {
  FOR each row t2 in T2 such that P2(t2,t3) {
    FOR each row t1 in T1 such that P1(t1,t2) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3; OUTPUT t;
      }
    }
  }
}

對于有外聯接的查詢,優化器可以只選擇這樣的順序:外表的環優先于內表的環。這樣,對于有外聯接的查詢,只可能有一種嵌套順序。在下面的查詢中,優化器將評估兩個不同的嵌套:

SELECT * T1 LEFT JOIN (T2,T3) ON P1(T1,T2) AND P2(T1,T3)
  WHERE P(T1,T2,T3)

嵌套為:

FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t2 in T2 such that P1(t1,t2) {
    FOR each row t3 in T3 such that P2(t1,t3) {
      IF P(t1,t2,t3) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f1:=TRUE
    }
  }
  IF (!f1) {
    IF P(t1,NULL,NULL) {
      t:=t1||NULL||NULL; OUTPUT t;
    }
  }
}

FOR each row t1 in T1 {
  BOOL f1:=FALSE;
  FOR each row t3 in T3 such that P2(t1,t3) {
    FOR each row t2 in T2 such that P1(t1,t2) {
      IF P(t1,t2,t3) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f1:=TRUE
    }
  }
  IF (!f1) {
    IF P(t1,NULL,NULL) {
      t:=t1||NULL||NULL; OUTPUT t;
    }
  }
}

在兩個嵌套中,必須在外環中處理T1,因為它用于外聯接中。T2T3用于內聯接中,因此聯接必須在內環中處理。但是,因為該聯接是一個內聯接,T2T3可以以任何順序處理。

當討論內聯接嵌套環的算法時,我們忽略了部分詳情,可能對查詢執行的性能的影響會很大。我們沒有提及所謂的“下推”條件。假定可以用連接公式表示我們的WHERE條件P(T1,T2,T3)

P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3)

在這種情況下,MySQL實際使用了下面的嵌套環方案來執行帶內聯接得到查詢:

FOR each row t1 in T1 such that C1(t1) {
  FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2)  {
    FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) {
      IF P(t1,t2,t3) {
         t:=t1||t2||t3; OUTPUT t;
      }
    }
  }
}

你會看見每個連接 C1(T1)C2(T2)C3(T3)被從最內部的環內推出到可以對它進行評估的最外的環中。如果C1(T1)是一個限制性很強的條件,下推條件可以大大降低從表T1傳遞到內環的行數。結果是查詢大大加速。

對于有外聯接的查詢,只有查出外表的當前的行可以匹配內表后,才可以檢查WHERE條件。這樣,對內嵌套環下推的條件不能直接用于帶外聯接的查詢。這里我們必須引入有條件下推前提,由遇到匹配后打開的標志保護。

對于帶下面的外聯接的例子

P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)

使用受保護的下推條件的嵌套環方案看起來應為:

FOR each row t1 in T1 such that C1(t1) {
  BOOL f1:=FALSE;
  FOR each row t2 in T2
      such that P1(t1,t2) AND (f1?C2(t2):TRUE) {
    BOOL f2:=FALSE;
    FOR each row t3 in T3
        such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) {
      IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) {
        t:=t1||t2||t3; OUTPUT t;
      }
      f2=TRUE;
      f1=TRUE;
    }
    IF (!f2) {
      IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) {
        t:=t1||t2||NULL; OUTPUT t;
      }
      f1=TRUE;
    }
  }
  IF (!f1 && P(t1,NULL,NULL)) {
      t:=t1||NULL||NULL; OUTPUT t;
  }
}

總的來說,可以從聯接條件(例如P1(T1,T2)P(T2,T3))提取下推前提。在這種情況下,下推前提也受一個標志保護,防止檢查由相應外聯接操作所產生的NULL-補充的行的斷言。

請注意如果從判斷式的WHERE條件推導出,根據從一個內表到相同嵌套聯接的另一個表的關鍵字進行的訪問被禁止。(在這種情況下,我們可以使用有條件關鍵字訪問,但是該技術還未用于MySQL 5.1中)

作者:mysql.com
來源:http://dev.mysql.com/doc/refman/5.1/zh/optimization.html
北斗有巢氏 有巢氏北斗