外连接简化
在许多情况下,查询中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