1.14Group By Optimization 分组优化

大多数方法为了满足分组查询需要扫描整个表并且创建一个临时表,其中每组中的值都是连续的,如果可以使用聚合函数和临时表获取各个分组。在某些情况下,mysql能够通过使用索引方法从而避免创建临时表来提高查询效率。

在group by中使用索引的前提条件是group by中的列都是来自相同的索引,并且索引中存储的是有序的key(例如,是一个BTREE索引,而不是HASH索引)。是否能用索引方法代替临时表的依据是哪一部分的索引被用在查询中,这一部分的条件是特殊的和聚合函数。

有两种方法在group by查询中使用索引方法,在接下来的部分详细描述。第一种方法,分组操作是用于任意范围谓词的使用。第二种方法是首先完成一个范围查询,然后对结果数据进行分组。

在mysql,GROUP BY被用于排序,所以服务器会使用order by优化器来分组。然而,并不赞成依赖GROUP BY的排序。请看8.2.1.13的order by优化

>松散索引扫描

最有效的处理分组的方法是在分组的列上直接使用索引。通过这种方法,mysql使用索引类型的特有属性如有序性(例如BTREE索引)。该特性使得查找分组在一个索引值上而不需要考虑所有满足where条件的全部索引的值。这种方法只会考虑索引的一小部分值,所以也被称为松散索引扫描。当没有一个where条件时,一个松散索引扫描读取的索引数量和分组数量相同,但依然要比全部索引值要少很多。如果where条件包含了范围查找,松散索引扫描每一组中满足范围条件的第一个key,并且再读取最少的可能数量的索引。这个可能性的条件如下:

1)查询覆盖了一张简单的表

2)group by中的列形式必须是满足索引左前缀,而不能再有其他列。(如果,除了group by,查询中有不同的条件,所有不同的属性引用列的形式是一个左前缀索引)例如,如果表t1中有一个索引在(c1,c2,c3),松散索引扫描可以使用,当查询有group by c1,c2。如果查询是group by c2,c3,或者是group by c1,c2,c4,则松散索引不可用。

3) 在查询的列展示中使用的聚合函数只有MIN()和MAX(),所有他们引用的都是相同的列。该列必须是一个索引并且必须直接跟在group by的后面。

4)除了group by引用的这些,剩下的索引中的其他部分必须都是常量(也就是说,他们必须引用的是常数类型),除了讨论MIN()或者是MAX()函数。

5)对于索引列,全部值必须都是被索引的,并不仅仅是一个前缀,例如,c1 VARCHAR(20),INDEX(c1(10)),索引不能被用于松散索引。

如果一个查询中使用了松散索引查询,EXPLAIN输出中会展示Using index for group-by在Extra列中。

假设t1(c1,c2,c3,c4)中有一个索引idx(c1,c2,c3),松散索引查询能够使用在如下的查询中:

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;

 如果查询中使用了where c3=const ,则松散索引扫描能够使用。

松散索引扫描方法能够使用在其他形式的聚合函数引用中出现在查询列表中,除了已经支持的MIN()和MAX()函数的引用:

 1)AVG(DISTINCT),SUM(DISTINCT),和COUNT(DISTINCT)也被支持。AVG(DISTINCT)和SUM(DISTINCT)使用单一参数。COUNT(DISTINCT)能够有多余一个列的参数。

 2)必须没有group by或者是DISTINCT条件在查询中。

 3)先前对松散扫描限制的描述任然适用。

假设在表t1(c1,c2,c3,c4)中有一索引idx(c1,c2,c3),那么松散索引扫描方法能够用于如下的查询:

SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;



SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;



>紧凑索引扫描

紧凑的索引扫描也许是一个全表索引扫描,也许是一个范围的索引扫描,完全由查询条件决定:

当对松散索引的查询条件不满足时,对于group by查询依然尽可能避免创建临时表。如果有一个范围查询在where条件中,该方法将只会读取满足where条件的全部key集合。否则,它将会是一个全索引的扫描。因为该方法会读取满足where条件的所有索引的值或者是对于没有范围条件的全部索引的值,我们定义它为一个紧凑的索引扫描。通过一个紧凑的索引扫描,分组操作通过读取满足条件的全部索引值来完成。

为了使用紧凑索引扫描方法,充分条件是存在一个恒定等价的条件对于查询中引用的全部列都来自group by中的key或者部分来自其中。恒等条件能填充任何对于查询keys的空白,所以可以是前缀索引。前缀索引能够被用于索引查找。如果我们要求对group by的结果排序,且形成查询key是前缀索引形式的是可能的,mysql也避免了额外的排序操作因为查询有序的前缀索引已经对所有的key排好序了。

假设有一个索引idx(c1,c2,c3)在t1(c1,c2,c3,c4)。下面的查询不能用之前描述的松散索引查询方法,但依然能够用紧凑索引查询方法。

1)group by中有一个空白,但是被条件c2='a'覆盖了。

SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;

2)group by不是以索引的第一部分开始的,但是查询条件中听了该部分的常量条件:

SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;

到此关于group by的优化就结束了,之后我们将介绍的是1.15的DISTINC Optimization


评论关闭
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!