8.2.1.15 GROUP BY 最佳化

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

GROUP BY使用索引的最重要前提是所有GROUP BY列都引用同一索引的属性,并且索引按 Sequences 存储其键(例如,对于BTREE索引,但对于HASH索引,则为真) 。临时 table 的使用是否可以由索引访问代替,还取决于查询中使用索引的哪些部分,为这些部分指定的条件以及所选的聚合函数。

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

在 MySQL 中,使用GROUP BY进行排序,因此服务器也可以将ORDER BY优化应用于分组。但是,不建议依赖隐式或显式GROUP BY排序。参见第 8.2.1.14 节“按优化排序”

索引扫描松散

处理GROUP BY的最有效方法是使用索引直接检索分组列。通过这种访问方法,MySQL 使用键排序的某些索引类型的属性(例如BTREE)。使用此属性,可以在索引中使用查找组,而不必考虑索引中满足所有WHERE条件的所有键。此访问方法仅考虑索引中的一部分键,因此称为“松散索引扫描”。如果没有WHERE子句,则“松散索引扫描”将读取与组数一样多的键,该数目可能比所有键的数目小得多。如果WHERE子句包含范围谓词(请参见第 8.8.1 节“使用 EXPLAIN 优化查询”中有关range连接类型的讨论),则“松散索引扫描”将查找满足范围条件的每个组的第一个键,并再次读取尽可能少的键数。在以下情况下可以这样做:

如果“宽松索引扫描”适用于查询,则EXPLAIN输出在Extra列中显示Using index for group-by

假设在 tablet1(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;

由于给出的原因,无法使用此快速选择方法执行以下查询:

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

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

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

假设在 tablet1(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查询创建临时 table。如果WHERE子句中有范围条件,则此方法仅读取满足这些条件的键。否则,它将执行索引扫描。由于此方法读取WHERE子句定义的每个范围内的所有键,或者在没有范围条件的情况下扫描整个索引,因此称为“紧索引扫描”。对于紧密索引扫描,仅在找到所有满足范围条件的键之后才执行分组操作。

为了使此方法起作用,对于查询中所有引用GROUP BY键的各个部分之前或之间的键的部分,都必须有一个恒定的相等条件。来自相等条件的常量将填充搜索键中的所有“间隙”,以便可以形成索引的完整前缀。这些索引前缀然后可以用于索引查找。如果GROUP BY结果需要排序,并且有可能形成作为索引前缀的搜索关键字,则 MySQL 也避免了额外的排序操作,因为在有序索引中使用前缀进行搜索已经按 Sequences 检索了所有关键字。

假设在 tablet1(c1,c2,c3,c4)上有一个索引idx(c1,c2,c3)。以下查询不适用于前面所述的“松散索引扫描”访问方法,但仍适用于“紧索引扫描”访问方法。

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