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:由於水平有限,譯文中難免存在謬誤,歡迎批評指正。