第7章:优化 / 7.2. 优化SELECT语句和其它查询 / 7.2.11. MySQL如何简化外部联合

在许多情况下,一个查询的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条件(或嵌入连接的联接条件,如果有)联接为一个连接。

当优化器为用外联接操作的联接查询评估方案时,它只考虑在访问内表之前访问外表的操作的方案。优化器选项受到限制,因为只有这样的方案允许我们用嵌套环机制执行带外联接操作的查询。

假定我们有一个下列形式的查询:

SELECT * T1 LEFT JOIN T2 ON P1(T1,T2)

  WHERE P(T1,T2) AND R(T2)

R(T2)大大减少了表T2中匹配的行数。如果我们这样执行查询,优化器将不会有其它选择,只能在访问表T2之前访问表T1,从而导致执行方案非常低。

幸运的是,如果WHERE条件拒绝nullMySQL可以将此类查询转换为没有外联接操作的查询。如果为该操作构建的NULL补充的行评估为FALSEUNKNOWN,则该条件称为对于某个外联接操作拒绝null

因此,对于该外联接:

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

类似下面的条件为拒绝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

检查一个外联接操作的条件是否拒绝null的总原则很简单。以下情况下为拒绝null的条件:

·         形式为A IS NOT NULL,其中A是任何内表的一个属性

·         包含内表引用的判断式,当某个参量为NULL时评估为UNKNOWN

·         包含用于连接的拒绝null的条件的联合

·         拒绝null的条件的逻辑和

一个条件可以对于一个查询中的一个外联接操作为拒绝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条件对于第2个外联接操作为拒绝null的但对于第1个不为拒绝null的。

如果WHERE条件对于一个查询中的一个外联接操作为拒绝null的,外联接操作被一个内联接操作代替。

例如,前面的查询被下面的查询代替:

SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A

                 INNER JOIN T3 ON T3.B=T1.B

  WHERE T3.C > 0

对于原来的查询,优化器将评估只与一个访问顺序T1T2T3兼容的方案。在替换的查询中,还考虑了访问顺序T3T1T2

一个外联接操作的转化可以触发另一个的转化。这样,查询:

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条件。在下面的查询中:

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
WHERE条件对于嵌入式外联接不为拒绝null的,但嵌入式外联接T2.A=T1.A AND T3.C=T1.C的联接条件为拒绝null因此该查询可以转换为

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