1. 程式人生 > >12.MySQL優化Outer Join Simplification講解

12.MySQL優化Outer Join Simplification講解

介紹

在許多情況下,簡化了查詢的FROM子句中的表的表示式。

在解析器階段,具有右外連線操作的查詢將轉換為僅包含左連線操作的等效查詢。

在一般情況下,轉換是這樣執行的:

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

等價的left join為:

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

所有的inner join表示式form T1 INNER JOIN T2 ON P(T1,T2)被替換成列表T1,T2, P(T1,T2)然後加入Where條件作為連線的條件(或嵌入連線的連線條件)

當優化器評估外部聯接操作的計劃時,它只考慮這樣的計劃,對於每個這樣的操作,外部表在內部表之前被訪問。優化器的選擇是有限的,因為只有這樣的計劃才能使用巢狀迴圈演算法來執行外部連線。

考慮此表單的查詢,其中R(T2)極大地縮小表T2中匹配行的數目:

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

如果查詢是按照寫入的方式執行的,那麼優化器別無選擇,只能在限制程度較高的表T2之前訪問限制程度較低的表T1,這可能產生非常低效的執行計劃。
相反,如果WHERE條件為null-rejected,MySQL會將查詢轉換為沒有外連線操作的查詢。(也就是說,它將外連線轉換為內連線)。一個outer join操作,假如他對任何NULL-complemented的一行生成的結果是FALSE or UNKNOWN那麼他是null-rejected的。

因此,對於這個外連線:

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

這些條件屬於null-rejected,因為他們對於任何null-complemented的行都不能為真(T2列設定為NULL):

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

以下這些條件是不能為null-rejected,因為對於null-complemented的行這些條件可能為真:

# 上面講解的比較繞,其實原因很簡單,因為下面的條件是T1.B OR T2.B所以滿足了T1的時候可能T2的值是NULL
T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3

檢查外部聯接操作的條件是否為空的一般規則很簡單:

  • 在建表時設定欄位A為 IS NOT NULL
  • 它包含一個null-rejected條件
  • 一個謂詞包含的參考是,當有一個引數為NULL時內連線的表的計算結果為UNKNOWN。
  • 是否為一個null-rejected條件的分離

一個條件在一個查詢的outer join條件中可以是null-rejected的而在另一個是非null-rejected的。在下面這個查詢中,對於第二個outer join條件他是null-rejected,但是對於第一個則不是。

# 這裡也比較繞,因為假如T3.B=T1.B不成立,T3的所有列的值都是NULL,所以T3.C > 0能去掉T3中所有不符合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

在一個查詢中如果某個outer join的條件是null-rejected的,那麼這個outer join條件可以轉變為inner join條件,上面的例子等價於如下SQL

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

對於原始的SQL,執行計劃計算之後只有一個訪問方式,T1、T2、T3,在改寫之後又多了一個可訪問方式T3、T1、T2。

一個outer join的轉換可以出發另外一個outer join的轉換。

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

其餘的outer join也可以被替換為inner join因為T3.B=T2.B是null-rejected,替換完後所有的outer join都消失了:

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

有時,一個成功的優化可以替換一個嵌入式的outer join操作,但是不能轉換一個嵌入式的outer join,舉例如下:

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

在查詢中轉換嵌入式outer join操作的任何嘗試都必須考慮嵌入outer join和WHERE條件的連線條件。

在這個查詢中,這個Where條件對於outer join不是null-rejected,但是這個join條件outer join條件T2.A=T1.A和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