Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


10.2.1.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, T2P(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-补全行来说都评估为 FALSEUNKNOWN

因此,对于这个外连接:

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

这样的条件是空值拒绝的,因为它们不能对任何 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 是任何内表的属性

  • 它是一个包含对内表的引用并且在其中一个参数为 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