Chinaunix首页 | 论坛 | 博客
  • 博客访问: 103728284
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类: Mysql/postgreSQL

2008-05-11 17:57:33

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

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;

   

SELECT * FROM t1

    WHERE key_part1=1

    ORDER BY key_part1 DESC, key_part2 DESC;

在某些情况下,MySQL不能使用索引来解决ORDER BY,尽管它仍然使用索引来找到匹配WHERE子句的行。这些情况包括:

·         对不同的关键字使用ORDER BY

·                SELECT * FROM t1 ORDER BY key1, key2

·         对关键字的非连续元素使用ORDER BY

·                SELECT * FROM t1 WHERE key2=constant ORDER BY key_part2

·         混合ASCDESC

·                SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC

·         用于查询行的关键字与ORDER BY中所使用的不相同:

·                SELECT * FROM t1 WHERE key2=constant ORDER BY key1

·         你正联接许多表,并且ORDER BY中的列并不是全部来自第1个用于搜索行的非常量表。(这是EXPLAIN输出中的没有const联接类型的第1个表)

·         有不同的ORDER BYGROUP BY表达式。

·         使用的表索引的类型不能按顺序保存行。例如,对于HEAP表的HASH索引情况即如此。

通过EXPLAIN SELECT ...ORDER BY,可以检查MySQL是否可以使用索引来解决查询。如果Extra列内有Using filesort,则不能解决查询。参见7.2.1节,“EXPLAIN语法(获取关于SELECT的信息)

文件排序优化不仅用于记录排序关键字和行的位置,并且还记录查询需要的列。这样可以避免两次读取行。文件排序算法的工作象这样:

1.    读行匹配WHERE子句的行,如前面所示。

2.    对于每个行,记录构成排序关键字和行位置的一系列值,并且记录查询需要的列。

3.    根据排序关键字排序元组

4.    按排序的顺序检索行,但直接从排序的元组读取需要的列,而不是再一次访问表。

该算法比以前版本的Mysql有很大的改进。

为了避免速度变慢,该优化只用于排序元组中的extra列的总大小不超过max_length_for_sort_data系统变量值的时候。(将该变量设置得太高的的迹象是将看到硬盘活动太频繁而CPU活动较低)

如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试下面的策略:

·         增加sort_buffer_size变量的大小。

·         增加read_rnd_buffer_size变量的大小。

·         更改tmpdir指向具有大量空闲空间的专用文件系统。该选项接受几个使用round-robin(循环)模式的路径。在Unix中路径应用冒号(:)区间开,在WindowsNetWareOS/2中用分号()。可以使用该特性将负载均分到几个目录中。注释:路径应为位于不同物理硬盘上的文件系统的目录,而不是同一硬盘的不同的分区。

默认情况下,MySQL排序所有GROUP BY col1col2...查询的方法如同在查询中指定ORDER BY col1col2...。如果显式包括一个包含相同的列的ORDER BY子句,MySQL可以毫不减速地对它进行优化,尽管仍然进行排序。如果查询包括GROUP BY但你想要避免排序结果的消耗,你可以指定ORDER BY NULL禁止排序。例如:

INSERT INTO foo

SELECT a, COUNT(*) FROM bar GROUP BY a ORDER BY NULL;

7.2.13. MySQL如何优化GROUP BY

满足GROUP BY子句的最一般的方法是扫描整个表并创建一个新的临时表,表中每个组的所有行应为连续的,然后使用该临时表来找到组并应用累积函数(如果有)。在某些情况中,MySQL能够做得更好,通过索引访问而不用创建临时表。

GROUP BY使用索引的最重要的前提条件是 所有GROUP BY列引用同一索引的属性,并且索引按顺序保存其关键字(例如,这是B-树索引,而不是HASH索引)。是否用索引访问来代替临时表的使用还取决于在查询中使用了哪部分索引、为该部分指定的条件,以及选择的累积函数。

有两种方法通过索引访问执行GROUP BY查询,如下面的章节所描述。在第1个方法中,组合操作结合所有范围判断式使用(如果有)。第2个方法首先执行范围扫描,然后组合结果元组。

7.2.13.1. 松散索引扫描

使用索引时最有效的途径是直接搜索组域。通过该访问方法,MySQL使用某些关键字排序的索引类型(例如,B-)的属性。该属性允许使用 索引中的查找组而不需要考虑满足所有WHERE条件的索引中的所有关键字。既然该访问方法只考虑索引中的关键字的一小部分,它被称为松散索引扫描。如果没有WHERE子句, 松散索引扫描读取的关键字数量与组数量一样多,可以比所有关键字数小得多。如果WHERE子句包含范围判断式(关于range联接类型的讨论参见7.2.1节,“EXPLAIN语法(获取关于SELECT的信息)), 松散索引扫描查找满足范围条件的每个组的第1个关键字,并且再次读取尽可能最少数量的关键字。在下面的条件下是可以的:

·         查询针对一个单表。

·         GROUP BY包括索引的第1个连续部分(如果对于GROUP BY,查询有一个DISTINCT子句,则所有显式属性指向索引开头)

·         只使用累积函数(如果有)MIN()MAX(),并且它们均指向相同的列。

·         索引的任何其它部分(除了那些来自查询中引用的GROUP BY)必须为常数(也就是说,必须按常量数量来引用它们),但MIN()MAX() 函数的参数例外。

此类查询的EXPLAIN输出显示Extra列的Using indexforgroup-by

下面的查询提供该类的几个例子,假定表t1(c1,c2,c3,c4)有一个索引idx(c1c2c3)

SELECT c1, c2 FROM t1 GROUP BY c1, c2;

SELECT DISTINCT c1, c2 FROM t1;

SELECT c1, MIN(c2) FROM t1 GROUP BY c1;

SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;

SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;

SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2

由于上述原因,不能用该快速选择方法执行下面的查询:

1.      除了MIN()MAX()还有其它累积函数,例如:

     SELECT c1, SUM(c2) FROM t1 GROUP BY c1;

2.      GROUP BY子句中的域不引用索引开头,如下所示:

     SELECT c1,c2 FROM t1 GROUP BY c2, c3;

3.      查询引用了GROUP BY部分后面的关键字的一部分,并且没有等于常量的等式,例如:

     SELECT c1,c3 FROM t1 GROUP BY c1, c2
阅读(553) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~