在两个嵌套中,必须在外环中处理T1,因为它用于外联接中。T2和T3用于内联接中,因此联接必须在内环中处理。但是,因为该联接是一个内联接,T2和T3可以以任何顺序处理。
当讨论内联接嵌套环的算法时,我们忽略了部分详情,可能对查询执行的性能的影响会很大。我们没有提及所谓的“下推”条件。假定可以用连接公式表示我们的WHERE条件P(T1,T2,T3):
P(T1,T2,T2) = C1(T1) AND C2(T2) AND C3(T3)。
在这种情况下,MySQL实际使用了下面的嵌套环方案来执行带内联接得到查询:
FOR each row t1 in T1 such that C1(t1) {
FOR each row t2 in T2 such that P1(t1,t2) AND C2(t2) { FOR each row t3 in T3 such that P2(t2,t3) AND C3(t3) { IF P(t1,t2,t3) {
t:=t1||t2||t3; OUTPUT t; } } } }
你会看见每个连接 C1(T1),C2(T2),C3(T3)被从最内部的环内推出到可以对它进行评估的最外的环中。如果C1(T1)是一个限制性很强的条件,下推条件可以大大降低从表T1传递到内环的行数。结果是查询大大加速。
对于有外联接的查询,只有查出外表的当前的行可以匹配内表后,才可以检查WHERE条件。这样,对内嵌套环下推的条件不能直接用于带外联接的查询。这里我们必须引入有条件下推前提,由遇到匹配后打开的标志保护。 对于带下面的外联接的例子
P(T1,T2,T3)=C1(T1) AND C(T2) AND C3(T3)
使用受保护的下推条件的嵌套环方案看起来应为:
FOR each row t1 in T1 such that C1(t1) { BOOL f1:=FALSE; FOR each row t2 in T2
such that P1(t1,t2) AND (f1?C2(t2):TRUE) { BOOL f2:=FALSE; FOR each row t3 in T3
such that P2(t2,t3) AND (f1&&f2?C3(t3):TRUE) { IF (f1&&f2?TRUE:(C2(t2) AND C3(t3))) { t:=t1||t2||t3; OUTPUT t; } f2=TRUE; f1=TRUE; }
IF (!f2) {
IF (f1?TRUE:C2(t2) && P(t1,t2,NULL)) { t:=t1||t2||NULL; OUTPUT t; } f1=TRUE;
} }
IF (!f1 && P(t1,NULL,NULL)) { t:=t1||NULL||NULL; OUTPUT t; } }
总的来说,可以从联接条件(例如P1(T1,T2)和P(T2,T3))提取下推前提。在这种情况下,下推前提也受一个标志保护,防止检查由相应外联接操作所产生的NULL-补充的行的断言。
请注意如果从判断式的WHERE条件推导出,根据从一个内表到相同嵌套联接的另一个表的关键字进行的访问被禁止。(在这种情况下,我们可以使用有条件关键字访问,但是该技术还未用于MySQL 5.1中)。
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,T2、P(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条件拒绝null,MySQL可以将此类查询转换为没有外联接操作的查询。如果为该操作构建的NULL补充的行评估为FALSE或UNKNOWN,则该条件称为对于某个外联接操作拒绝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
对于原来的查询,优化器将评估只与一个访问顺序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条件。在下面的查询中:
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
7.2.12. MySQL如何优化ORDER BY
在某些情况中,MySQL可以使用一个索引来满足ORDER BY子句,而不需要额外的排序。
即使ORDER BY不确切匹配索引,只要WHERE子句中的所有未使用的索引部分和所有额外的ORDER BY 列为常数,就可以使用索引。下面的查询使用索引来解决ORDER BY部分: SELECT * FROM t1
ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1
WHERE key_part1=constant ORDER BY key_part2;
SELECT * FROM t1
ORDER BY key_part1 DESC, key_part2 DESC;