MySQL查询优化之group by的优化

原文地址:https://dev.mysql.com/doc/refman/5.7/en/group-by-optimization.html

译文:

8.2.1.15 GROUP BY优化

满足GROUP BY子句的最常用方法是扫描整个表并创建一个新的临时表,其中每个组的所有行都是连续的,然后使用这个临时表查找组并应用聚合函数(如果有的话)。在一些情况下,MySQL可以做的更好,通过使用索引访问的方法避免创建临时表。

使用索引处理GROUP BY的前提条件是group by列引用的属性都来自同一个索引,而且索引按照顺序存储它其中的键(例如,对于BTREE索引是正确的,但是对于哈希索引则不是)。在一个查询中,使用临时表是否会被使用索引方法所代替取决于索引的哪一部分被使用、为这些部分指定的条件以及所选择的聚合函数。

有两种方法可以通过索引访问执行GROUP BY查询,下文会进行详细的介绍。第一种方法将分组操作与所有范围谓词(如果有)一起应用。第二种方法先执行一个范围扫描,然后对结果元组进行分组。

在MySQL中,GROUP BY用于排序,所以服务器也可能对分组操作使用ORDER BY优化。但是,不建议依赖于隐式或显式的GROUP BY分组。可以参考Section 8.2.1.14, “ORDER BY Optimization”.

松散索引扫描

处理GROUP BY最有效的方法是使用一个索引直接检索分组列。对于这种访问方法,MySQL使用了一些类型的索引中的键是有序得这个性质(例如,BTREE索引)。该属性允许在索引中使用查找组,而不必考虑满足所有WHERE条件的索引中的所有键。这种访问方法只考虑索引中的一小部分键,因此称为松散索引扫描。当没有WHERE子句时,松散索引扫描读取的键数与组数相同,而组数可能是一个比所有键数小得多的数字。如果WHERE子句包含范围谓词(请参阅Section 8.8.1, “Optimizing Queries with EXPLAIN”),那么松散的索引扫描将查找每个满足范围条件的组的第一个键,并再次读取可能的最小键数。在下列情况下,这是可能的:

    1)查询在单个表上;

    2)GROUP BY只列举构成索引的最左边的前缀的列,没有其他列。(如果查询有一个DISTINCT子句,而不是GROUP BY,则所有不同的属性都指的是构成索引最左边前缀的列)。例如,如果表t1有一个建立在(c1,c2,c3)上的索引且查询中有GROUPBY c1,c这样的语句,则可以使用松散索引扫描。

    3)在查询列表中使用的唯一聚合函数(如果有的话)是MIN()和MAX(),它们都引用同一列。列必须在索引中,并且必须立即跟随GROUP BY中的列。

    4)除了查询中GROUP BY引用的索引列之外,索引的任何其他部分都必须是常量(也就是说,它们必须以与常量相等的方式引用),不包括MIN()或MAX()函数的参数。

    5)对于索引中的列,必须索引完整的列值,而不仅仅是前缀。例如,对于c1 VARCHAR(20),INDEX (c1(10),索引只使用c1值的前缀,不能用于松散的索引扫描。

如果松散扫描索引对一个查询来说是适用的,explain的输出结果中会在Extra列显示Using index for group-by。

假设表t1(c1,c2,c3)上有一个索引idx(c1,c2,c3,c4)。松散索引扫描访问方法可用于以下查询:

  • 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;

   如果3)中的查询包含c3 = const,则可以使用松散索引扫描。

除了已经支持的聚合函数MIN()和MAX()的引用之外,松散索引扫描访问方法还可以应用于select列表中其他形式的聚合函数引用:

    1)支持AVG(DISTINCT),SUM(DISTINCT),和COUNT(DISTINCT)。AVG(DISTINCT)和SUM(DISTINCT)接受单一参数,COUNT(DISTINCT)可以接受多列作为参数;

    2)在查询中必须没有GROUP BY或者DISTINCT子句;

    3)前面描述的松散索引扫描限制仍然适用。

假设表t1(c1,c2,c3)上有一个索引idx(c1,c2,c3,c4)。松散索引扫描访问方法可用于以下查询:

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

严格索引扫描

紧密索引扫描可以是完全索引扫描,也可以是范围索引扫描,这取决于查询条件。

当不满足松散索引扫描的条件时,仍然可以避免为GROUP BY查询创建临时表。如果WHERE子句中有范围条件,严格索引扫描只读取满足这些条件的键。否则,它将执行索引扫描。因为该方法读取WHERE子句定义的每个范围中的所有键,或者在没有范围条件的情况下扫描整个索引,所以称为严格索引扫描。在使用严格索引扫描的情况下,只有在找到所有满足范围条件的键之后才执行分组操作。

要使此方法发挥作用,查询中的所有列都有一个常量相等条件就足够了,该条件指的是键的部分列在GROUP BY子句中的键列的前面或中间。等式条件中的常量填充搜索键中的任何“缺口”,这样就可以形成索引的完整前缀。这些索引前缀可以用于索引查找。如果分组后的结果需要排序,那么它就可能形成作为索引前缀的搜索键,MySQL还避免了额外的排序操作,因为在有序索引中使用前缀进行搜索已经按顺序检索了所有键。

假设表t1(c1,c2,c3)上有一个索引idx(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;

PS:由于水平有限,译文中难免存在谬误,欢迎批评指正。

猜你喜欢

转载自blog.csdn.net/qq_41080850/article/details/85405805