Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

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 补充行评估为 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