18.MySQL优化GROUP BY Optimization

介绍

最常用的优化 GROUP BY 的方式就是扫描整张表然后创建临时表在临时表中保证所有数据分成不同的组,每个组里的数据都是连续的,然后利用临时表来发现组并应用聚合函数(假如有的话)。在某些情况下,MySQL可以做的更好,它可以使用索引避免使用临时表。

使用索引优化GROUP BY的一个非常重要的先决条件是,GROUP BY中引用的属性需要在同一个索引中,并且索引是有序的(举个例子,可以使用Btree索引,但不能使用Hash索引)。是否使用索引扫描替换临时表取决于在一个查询中使用索引的哪个部分,为这些部分指定的条件以及所选的聚合函数。

如下所述,有两种方法可以通过索引执行GROUP BY查询。。第一种方法将分组操作与所有范围谓词(如果有)一起应用。第二种方法首先执行范围扫描,然后对生成的元组进行分组。

  • Loose Index Scan
  • Tight Index Scan

Loose Index Scan在没有GROUP BY的情况下也可以使用松散索引扫描

Loose Index Scan

一个最有效的方式优化GROUP BY就是索引直接扫描GROUP BY的列。使用此方法访问,使用了索引的键的有序性这个属性(Btree索引)。此属性允许在索引中使用查找组,而不必考虑索引中满足所有条件的所有键。如果没有WHERE 子句,则松散索引扫描会读取与group数一样多的keys,这可能比所有keys的数量小得多。如果一个WHERE条件包含范围扫描的谓词,一个Loose Index Scan会在满足WHERE条件的group中查询每个组的第一个keys,并再次读取尽可能少的键。它可能按照以下步骤进行:

  • 在一个表上查询
  • 这个GROUP BY仅仅使用一个索引的最左前缀命名不包含其他列。(如果GROUP BY查询具有DISTINCT子句,则所有不同的属性引用形成索引的最左前缀的列。)举个例子,假如有一张表t1包含索引(c1,c2,c3),Loose Index Scan可以应用于GROUP BY c1,c2但是不能应用于GROUP BY c2,c3(因为这俩不是最左前缀),也不能用于GROUP BY c1,c2,c4(c4不在索引中)。
  • 在select列表中的唯一的聚合函数,并且它们都引用同一列。这个列必须在index中,并且仅仅跟着GROUP BY的列。
  • 索引中除查询中引用的GROUP BY之外的任何其他部分必须是常量。(也就是说,他们必须与常量相等),除了除了MIN()或MAX()函数的参数。
  • 在索引中的列,整行的值都必须在索引中,不能是前缀。举个例子,c1是VARCHAR(20),索引是 (c1(10)), 这个索引用了c1的一部分前缀,所以无法使用索引。

如果使用了Loose Index Scan来优化了查询,那么 EXPLAIN 那么Extra中会显示 Using index for group-by 。

这有个索引idx(c1,c2,c3)在表t1(c1,c2,c3,c4)。 Loose Index Scan可以用于以下查询:

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;

以下查询无法使用快速检索,原因如下:

  • 聚合函数不是 MIN() or MAX():

      SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
    
  • 不遵循索引的最左原则

      SELECT c1, c2 FROM t1 GROUP BY c2, c3;
    
  • 查询引用了GROUP BY部分之后的键的一部分,并且与常量不相等:

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

    当增加条件WHERE c3 = const, Loose Index Scan可以被使用

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

  • 支持AVG(DISTINCT), SUM(DISTINCT)和 COUNT(DISTINCT)。AVG(DISTINCT) 并SUM(DISTINCT)有一个列。 COUNT(DISTINCT)可以有多个列。
  • 查询中必须没有GROUP BY或DISTINCT子句。
  • 以上描述的松散索引限制仍然适用。

这有个索引idx(c1,c2,c3)在表t1(c1,c2,c3,c4)。 Loose Index Scan可以用于以下查询:

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

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

Tight Index Scan

Tight Index Scan可以使索引全扫描,也可以是索引范围扫描,这取决于查询条件。

如果不满足Loose Index Scan的条件,也可以避免GROUP BY的时候创建临时表。假如再WHERE条件里有范围条件,这个方法仅读取满足那些条件的keys。否则,他执行索引扫描。因为这个方法读取每个WHERE条件中每一个范围条件的所有keys,或者扫描整个索引假如这个SQL离不包含范围条件,这样的查询模式可以叫做Tight Index Scan。在Tight Index Scan的扫描方式中,仅在找到grouping中的所有keys之后才执行分组操作。

想要这个优化方式起作用,对于查询中的所有列,只要有一个常数相等条件就足够了,该条件是指在GROUP BY的列的部分之前或部分之间未出现的列被定义为常量。一个常数的等价条件用来填充"gaps"用来形成所以的完整前缀。这个索引前缀可以用于索引查询。假如GROUP BY的查询结果需要排序,并且可以形成作为索引前缀的搜索键,MySQL还避免了额外的排序操作,因为在有序索引中使用前缀进行搜索已经按顺序检索了所有键。

假设idx(c1,c2,c3)表上 有索引 t1(c1,c2,c3,c4)。以下查询不适用于前面介绍的松散索引扫描访问方法,但仍可使用紧密索引扫描访问方法。

  • 这有一个gap,但是他通过c2='a’填补上了

      SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
    
  • GROUP BY的条件不是从索引的第一列开始,但是通过c1 = 'a’填补上了条件

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

猜你喜欢

转载自blog.csdn.net/ciqingloveless/article/details/83988555