MySQL查询优化之八-外部Join简化(Outer Join Simplification)

268 阅读4分钟

MySQL查询优化之八-外部Join简化(Outer Join Simplification)


如需转载请标明出处
QQ技术交流群:129518033

环境:
MySQL版本:5.5.15
操作系统:windows

本文讨论外部Join简化(Outer Join Simplification)。

在很多情况下,查询的FROM子句中的表达式被简化了。

在解析器阶段,具有右外join操作的查询将转换为仅包含左join操作的等同查询。 在一般情况下,转换是这样进行的:

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

等效的左join:

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

所有形式为T1 INNER JOIN T2 ON P(T1,T2)的内部join表达式都被替换为列表T1,T2,P(T1,T2)作为与WHERE条件(或者联接条件 嵌入连接,如果有的话)。

当优化器评估外连接操作计划时,它只考虑计划,对于每个这样的操作,在内表之前访问外部表。 优化器的选择是有限的,因为只有这样的计划才能使用嵌套循环算法执行外部join。

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

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

如果查询按照书面执行,则优化器别无选择,只能访问限制较多的表T2之前的限制较少的表T1,这可能会产生非常低效的执行计划。

相反,如果WHERE条件为空,则MySQL将查询转换为不含外join操作的查询。 (也就是说,它将外部join转换为内部join。)如果对于为该操作生成的任何NULL补充行,它的计算结果为FALSE或UNKNOWN,则说明该条件为null时会拒绝外部join操作。

因此,对于这个外join:

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

用于检查外部join操作的条件是否为空的一般规则很简单:

  • 它的形式是A IS NOT NULL,其中A是任何内部表的属性
  • 它是一个谓词,包含对内部表的引用,当其中一个参数为NULL时,该内部表的计算结果为UNKNOWN
  • 它是一个包含空拒绝条件的联合
  • 它是无效拒绝条件的分解

对于查询中的一个外join操作,条件可以为空,否则为空。 在此查询中,第二个外join操作的WHERE条件为空,但第一个外join操作不为null:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A
                 LEFT JOIN T3 ON T3.B=T1.B
  WHERE T3.C > 0

如果查询中的外join操作的WHERE条件为空,则外join操作被内join操作取代。

例如,在前面的查询中,第二个外join是null拒绝的,并且可以由内join替换:

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。

一个外部join操作的转换可能触发另一个的转换。 因此,查询:

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

其余的外部join操作也可以由内部join替换,因为条件T3.B = T2.B为空 - 拒绝。 这导致一个查询没有外join:

SELECT * FROM (T1 INNER JOIN T2 ON T2.A=T1.A), T3
  WHERE T3.C > 0 AND T3.B=T2.B

有时候,优化器会成功替换嵌入的外部join操作,但不能转换嵌入的外部join。 以下查询:

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

只能对仍包含嵌入外join操作的表单重写:

SELECT * FROM T1 LEFT JOIN
              (T2,T3)
              ON (T2.A=T1.A AND T3.B=T2.B)
  WHERE T3.C > 0

任何尝试在查询中转换嵌入的外join操作都必须考虑嵌入外join的连接条件以及WHERE条件。 在此查询中,对于嵌入式外join,WHERE条件不为null,但嵌入外join 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

Reference:
dev.mysql.com/doc/refman/…


如需转载请标明出处
QQ技术交流群:129518033