外连接简化

来自泡泡学习笔记
BrainBs讨论 | 贡献2023年7月18日 (二) 07:18的版本 (创建页面,内容为“在许多情况下,查询中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 的列表,…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

在许多情况下,查询中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 的列表,并作为连词与 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(即,将外连接转换为内连接),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


对于检查条件是否被null拒绝进行外连接操作的一般规则很简单:

  • 条件的形式为 A IS NOT NULL,其中 A 是任一内部表的属性
  • 条件是一个谓词,包含对内部表的引用,当其参数为NULL时,评估结果为UNKNOWN
  • 条件是一个包含null被拒绝条件的合取式
  • 条件是多个null被拒绝条件的析取式


在查询中,一个条件可能对某个外连接操作被null拒绝,而对另一个外连接操作则不被null拒绝。在以下查询中,WHERE条件对第二个外连接操作被null拒绝,但对第一个外连接操作则不被null拒绝:

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被null拒绝。这导致查询中没有任何外连接:

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条件对于嵌套外连接来说不是被null拒绝的,但是embedding外连接的连接条件T2.A=T1.A AND T3.C=T1.C是被null拒绝的:

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