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