Note:
本文背景大部分描述来自于 MySQL 官网,感兴趣的可以看原文描述
outer join simple
背景
outer join 的简化
- right JOIN 的处理
MySQL 通常在解析阶段,就将 RIGHT JOIN 转换为 LEFT JOIN 的等效查询,例如:
`(T1, ...) RIGHT JOIN (T2, ...) ON P(T1, ..., T2, ...)`
等价于
(T2, ...) LEFT JOIN (T1, ...) ON P(T1, ..., T2, ...)
JOIN 的驱动表顺序变成在,T1 INNER JOIN t2 ON P(T1,T2)
变成 T1, T2, P(T1, T2)
但优化器对于驱动表顺序的选择依然有限,仅能考虑 T1 在前的情况,可以进一步尝试
- inner JOIN 转换
例如:
SELECT * T1 FROM T1 LEFT JOIN T2 ON P1(T1,T2) WHERE P(T1,T2) AND R(T2)
如果该 SQL 按着此顺序执行,优化器只能按着 T1 驱动 T2 顺序执行,可可能会产生非常低效率的执行计划。
相反,如果 WHERE 条件是一个 null-rejected 的谓词,MySQL 可以将此类查询转换为一个非 outer join(inner join).
一个条件如果对于所有 NULL-complemented 的行来说,都会被评估为 FALSE 或者 UNKWON,则该条件对于 outer join 而言是 null-rejected.
null-rejected
一个条件如果对于所有 NULL-complemented 的行来说,都会被评估为 FALSE 或者 UNKWON,则该条件对于 outer join 而言是 null-rejected.
例如,对于以下外部连接:
T1 LEFT JOIN T2 ON T1.A=T2.A
以下条件会被视为 null-rejected,因为对于任何一个 NULL-complemented 的行,此条件都不可能为 true
T2.B IS NOT NULL
T2.B > 3 T2.C <= T1.C
T2.B < 2 OR T2.C > 1
而以下条件不会被视为 null-rejected,因为对于任何一个 NULL-complemented 的行,此条件可能为 true
T2.B IS NULL
T1.B < 3 OR T2.B IS NOT NULL
T1.B < 3 OR T2.B > 3
一个条件对于 outer join 而言,为 null-rejected 的通用规则为:
- It is of the form A IS NOT NULL, where A is an attribute of any of
the inner tables - It is a predicate containing a reference to an inner table that evaluates to UNKNOWN when one of its arguments is NULL
- It is a conjunction containing a null-rejected condition as a conjunct
- It is a disjunction of null-rejected conditions
一个条件对于 outer join 查询中的一个 outer join 可能是 null-rejected 但对于另一个不是 null-rejeced.
例如,下面 WHERE 条件对于第二个 outer join 是 null-rejected 的,但对第一个 outer join 不是:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A=T1.A LEFT JOIN T3 ON T3.B=T1.B WHERE T3.C > 0如果一个 WHERE 条件对于一个 outer join 而言是一个 null-rejected 的话,则 outer join 可以转换为 inner join.
例如,上述 SQL 可以转换为:
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 被拒绝为空。这会导致查询完全没有外部连接:
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```