8.2.1.9 外连接简化

在许多情况下,查询的FROM子句中的 tabletable 达式都得到了简化。

在解析器阶段,具有右外部联接操作的查询将转换为仅包含左联接操作的等效查询。在一般情况下,执行转换时要进行以下右连接:

(T1, ...) RIGHT JOIN (T2, ...) ON P(T1, ..., T2, ...)

成为以下等效的左联接:

(T2, ...) LEFT JOIN (T1, ...) ON P(T1, ..., T2, ...)

形式T1 INNER JOIN T2 ON P(T1,T2)的所有内部联接 table 达式都由列 tableT1,T2替换,列 tableP(T1,T2)WHERE条件(或嵌入联接的联接条件,如果有的话)一起联接。

当优化程序评估外部联接操作的计划时,它仅考虑计划,其中对于每个此类操作,在访问内部 table 之前先访问外部 table。由于只有这样的计划才能使用嵌套循环算法执行外部联接,因此优化器的选择受到限制。

考虑这种形式的查询,其中R(T2)大大缩小了 tableT2中匹配行的数量:

SELECT * T1 LEFT JOIN T2 ON P1(T1,T2)
  WHERE P(T1,T2) AND R(T2)

如果查询是按书面形式执行的,则优化器别无选择,只能在限制程度较高的 tableT2之前访问限制程度较小的 tableT1,这可能会产生效率很低的执行计划。

相反,如果WHERE条件为空,MySQL 会将查询转换为无外部联接操作的查询。 (也就是说,它将外部联接转换为内部联接.)如果外部联接操作为该操作生成的任何NULL补码行求值为FALSEUNKNOWN,则该条件被认为是 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补行可能为真:

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是任何内部 table 的属性

  • 这是一个谓词,包含对内部 table 的引用,该内部 table 的参数之一为NULL时其值为UNKNOWN

  • 它是一个包含空值拒绝条件作为合取词的连词

  • 它是零值拒绝条件的析取

对于查询中的一个外部联接操作,条件可以为 null 拒绝,而对于另一项则不能为 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

对于原始查询,优化器仅评估与单个 table 访问 SequencesT1,T2,T3兼容的计划。对于重写的查询,它另外考虑访问 SequencesT3,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

只能将其重写为仍包含嵌入外部联接操作的 table 单:

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