Documentation Home
MySQL 8.0 参考手册  / 第8章优化  / 8.2 优化SQL语句  / 8.2.1 优化 SELECT 语句  /  8.2.1.10 外部连接简化

8.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)都被 list 替换 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,这可能会产生非常低效的执行计划。

WHERE相反,如果条件为 null-rejected ,MySQL 会将查询转换为没有外部连接操作的查询。(也就是说,它将外连接转换为内连接。)如果一个条件的计算结果为 FALSEUNKNOWN用于为该操作生成的任何 NULL补充行,则称该条件对于外连接操作是空拒绝的。

因此,对于这个外部连接:

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 拒绝,因为它们可能适用于NULL-complemented 行:

T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3

检查外连接操作的条件是否为 null-rejected 的一般规则很简单:

  • 它的形式为A IS NOT NULL,其中 A是任何内表的属性

  • 它是一个包含对内表的引用的谓词,UNKNOWN当其参数之一为NULL

  • 它是一个连词,包含一个空拒绝条件作为连词

  • 它是空拒绝条件的析取

对于查询中的一个外连接操作,一个条件可以被空值拒绝,而对于另一个条件则不能被空值拒绝。在此查询中,第二个外连接操作的WHERE条件为 null-rejected,但对于第一个外连接操作,条件不是 null-rejected:

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-rejected,则外连接操作将替换为内连接操作。

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

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 是 null-rejected。这导致查询根本没有外连接:

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