在许多情况下,查询的 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)
形式的所有内连接表达式都将替换为列表 T1,T2
,P(T1,T2)
作为合取式连接到 WHERE
条件(或者,如果有嵌入连接,则连接到嵌入连接的连接条件)。
当优化器评估外连接操作的计划时,它只会考虑对每个此类操作在外表访问在内表之前的计划。优化器的选择是有限的,因为只有此类计划才能使用嵌套循环算法执行外连接。
考虑这种形式的查询,其中 R(T2)
极大地缩小了表 T2
中匹配行的数量
SELECT * T1 FROM T1
LEFT JOIN T2 ON P1(T1,T2)
WHERE P(T1,T2) AND R(T2)
如果按原样执行查询,优化器别无选择,只能在访问限制较多的表 T2
之前访问限制较少的表 T1
,这可能会产生非常低效的执行计划。
相反,如果 WHERE
条件被空值拒绝,MySQL 会将查询转换为没有外连接操作的查询。(也就是说,它会将外连接转换为内连接。)如果对于为操作生成的任何 NULL
补充行,条件的计算结果为 FALSE
或 UNKNOWN
,则称该条件对于外连接操作被空值拒绝。
因此,对于此外连接
T1 LEFT JOIN T2 ON T1.A=T2.A
诸如此类的条件被空值拒绝,因为它们对于任何 NULL
补充行(T2
列设置为 NULL
)都不可能是 true
T2.B IS NOT NULL
T2.B > 3
T2.C <= T1.C
T2.B < 2 OR T2.C > 1
诸如此类的条件不会被空值拒绝,因为它们对于 NULL
补充行可能是 true
T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3
检查条件是否对某个外连接操作被空值拒绝的一般规则很简单
它的形式为
A IS NOT NULL
,其中A
是任何内表的属性它是一个包含对内表的引用的谓词,当其参数之一为
NULL
时,其计算结果为UNKNOWN
它是一个包含空值拒绝条件作为合取式的合取式
它是空值拒绝条件的析取式
一个条件对于查询中的一个外连接操作可以被空值拒绝,而对于另一个外连接操作则不能被空值拒绝。在这个查询中,WHERE
条件对于第二个外连接操作被空值拒绝,但对于第一个外连接操作则不被空值拒绝
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
LEFT JOIN T3 ON T3.B=T1.B
WHERE T3.C > 0
如果查询中的外连接操作的 WHERE
条件被空值拒绝,则该外连接操作将替换为内连接操作。
例如,在前面的查询中,第二个外连接被空值拒绝,可以用内连接替换
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
被空值拒绝。这会生成一个完全没有外连接的查询
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
条件对于嵌入式外连接来说不是空拒绝的,但是嵌入式外连接 T2.A=T1.A AND T3.C=T1.C
的连接条件是空拒绝的。
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