查询的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)
如果查询按照原样执行,优化器没有选择,但只能访问不受限制的表 T1
之前访问受限制的表 T2
,这可能会产生非常低效的执行计划。
相反,MySQL 将查询转换为没有外连接操作的查询,如果 WHERE 条件为空拒绝。(也就是说,它将外连接转换为内连接。)条件被认为为空拒绝,如果它对任何生成的 NULL 补充行评估为 FALSE
或 UNKNOWN
。
因此,对于这个外连接:
T1 LEFT JOIN T2 ON T1.A=T2.A
这些条件被认为为空拒绝,因为它们不能为任何 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
是任何内表的属性 -
它是一个谓词,包含对内表的引用,评估为
UNKNOWN
当其一个参数为NULL
时 -
它是一个包含空拒绝条件的合取式
-
它是一个空拒绝条件的析取式
一个条件可以为空拒绝一个外连接操作,但不是另一个外连接操作。在这个查询中,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