Skip to main content

10.外连接简化

在许多情况下,查询的 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,这可能会产生非常低效的执行计划。

相反,如果 WHERE 条件拒绝空值,MySQL 会将查询转换为不带外连接操作的查询。 (也就是说,它将外连接转换为内连接。)如果对于为该操作生成的任何 NULL 补足行,该条件的计算结果为 FALSE 或 UNKNOWN,则该条件被认为对于外连接操作是 null 拒绝的。

因此,对于这个外连接:

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

诸如此类的条件将被 null 拒绝,因为它们对于任何 NULL 补足行(T2 列设置为 NULL)都不能成立:

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

诸如此类的条件不会被 null 拒绝,因为它们对于 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,其中 A 是任何内表的属性
  • 它是一个包含对内表的引用的谓词,当其参数之一为 NULL 时,该内表的计算结果为 UNKNOWN
  • 它是一个包含空拒绝条件作为连词的连词
  • 它是零拒绝条件的析取(It is a disjunction of null-rejected conditions)

对于查询中的一个外连接操作,条件可以拒绝空值,而对于另一个外连接操作则不能拒绝空值。 在此查询中,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 条件拒绝 null,则外连接操作将替换为内连接操作。

例如,在前面的查询中,第二个外连接是拒绝 null 的,可以用内连接替换:

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