1. 程式人生 > >MySQL查詢優化之外連線簡化

MySQL查詢優化之外連線簡化

原文地址:https://dev.mysql.com/doc/refman/5.7/en/outer-join-simplification.html

譯文:

8.2.1.9 外連線簡化

在很多情況下,查詢的from子句中的表表達式都可以被簡化。

在解析階段,帶有右外連線操作的查詢會被轉化成包含左外連線操作的查詢。一般情況下,右外連線的轉換是這樣執行的:

(T1, ...) RIGHT JOIN (T2, ...) ON P(T1, ..., T2, ...)

上述右外連線的等價左外連線為:

(T2, ...) LEFT JOIN (T1, ...) ON P(T1, ..., T2, ...)

T1 inner join T2 ON P(T1,T2) 這樣形式的內連線表示式,會被與where條件結合的列表T1,T2,P(T1,T2)所替換(或者是與巢狀連線的連線條件結合的列表T1,T2,P(T1,T2)替換)。

當優化器評估外連線操作的計劃時,它只會把外部表在內部表之前訪問這樣的每個計劃考慮進去。優化器的選擇是有限的,因為只有這樣的計劃才能使用nested-loop演算法執行外部連線。

考慮一個這樣的查詢,其中,R(T2)大大地減少了從表T2中匹配的行數:

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

如果查詢按照編寫的那樣執行,優化器除了先訪問限制較少的表T1再訪問限制較多的表T2外別無選擇,這可能會產生一個非常低效率的執行計劃。

相反,當where條件是拒絕NULL值(null-reject)時,MySQL會把外連線轉化成內連線。如果外連線操作的條件對於為該操作生成的任何空補行計算為False或unknown,則該條件稱為null-reject條件。

因此,對於下面的外連線:

T1 LEFT JOIN T2 ON T1.A=T2.A

如下所寫的條件都是null-rejected,因為對於任何的空補行來說,它們的值都不可能是True(T2列被設為NULL):

T2.B IS NOT NULL
T2.B > 3
T2.C <= T1.C
T2.B < 2 OR T2.C > 1

如下所寫的條件都不是null-rejected,因為對於謀個空補行來說,它們的值可能是True:

T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3

檢查外部連線操作的條件是否為null-rejected的一般規則很簡單:

    1)它的形式是A IS NOT NULL ,其中,A是內部表中的任意一個屬性;

    2)一個包含內部表引用的謂詞,當其中一個引數是NULL時,它被評估為UNKNOWN;

    3)包含null-rejected條件的結合;

    4)null-rejected條件的分裂。

查詢中的某個條件可以同時是一個外連線操作的null-rejected條件,又是另一個外連線操作的非null-rejected條件。在下面的查詢中,where條件就既是第二個外連線操作的null-rejected條件,又是第一個外連線操作的非null-rejected條件:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0

如果查詢中的where條件是一個外連線操作的null-rejected條件,該外連線操作會被替換成內連線操作。例如,上面的查詢中,where條件是第二個外連線操作的null-rejected條件,因此,它可以被替換成內連線操作:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 INNER JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0

對於原始查詢,優化器只計算與單個表訪問順序T1、T2、T3相容的計劃。對於重寫的查詢,它還會考慮訪問順序T3、T1、T2。

一個外連線操作的轉換可能會觸發另一個外連線操作的轉換。因此,查詢:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T2.B
  WHERE T3.C > 0

首先被轉換成查詢:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 INNER JOIN T3 ON T3.B=T2.B
  WHERE T3.C > 0

相當於查詢:

SELECT * FROM (T1 LEFT JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B

剩下的外連線操作可以繼續被轉換成內連線,因為條件T3.B=T2.B是null-rejected條件。這從而產生了一個沒有任何外連線的查詢:

SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B

有時,優化器可以成功的替換一個巢狀外連線,但是卻不能轉換巢狀外連線。如下面的查詢:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A
  WHERE T3.C > 0

被轉換成:

SELECT * FROM T1 LEFT JOIN
              (T2 INNER JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A
  WHERE T3.C > 0

它可以被重寫為仍然包含巢狀外連線操作的形式:

SELECT * FROM T1 LEFT JOIN
              (T2,T3)
              ON (T2.A=T1.A AND T3.B=T2.B)
  WHERE T3.C > 0

任何想要轉換查詢中外連線操作的嘗試,都必須要同時考慮到where條件和巢狀外連線的連線條件。在下面的查詢中,where條件不是巢狀外連線的null-rejected條件,但是巢狀外連線的連線條件T2.A=T1.A AND T3.C=T1.C是null-rejected條件:

SELECT * FROM T1 LEFT JOIN
              (T2 LEFT JOIN T3 ON T3.B=T2.B)
              ON T2.A=T1.A AND T3.C=T1.C
  WHERE T3.D > 0 OR T1.D > 0

因此,上述查詢可以轉換為:

SELECT * FROM T1 LEFT JOIN
              (T2, T3)
              ON T2.A=T1.A AND T3.C=T1.C AND T3.B=T2.B
  WHERE T3.D > 0 OR T1.D > 0

PS:由於水平有限,譯文中難免存在謬誤,歡迎批評指正。