#### 8.2.1.17 GROUP BY 优化

[](<>)[](<>)

满足一个最一般的方法`通过...分组`子句是扫描整个表并创建一个新的临时表，其中每个组的所有行都是连续的，然后使用此临时表来发现组并应用聚合函数（如果有）。在某些情况下，MySQL 能够做得比这更好，并通过使用索引访问来避免创建临时表。

使用索引最重要的前提条件`通过...分组`就这些吗`通过...分组`列引用来自同一索引的属性，并且索引按顺序存储其键（例如，对于`BTREE`索引，但不适用于`哈希`指数）。是否可以使用索引访问替换临时表的使用还取决于查询中使用了索引的哪些部分、为这些部分指定的条件以及所选的聚合函数。

有两种方法可以执行`通过...分组`通过索引访问进行查询，详见以下部分。第一种方法将分组操作与所有范围谓词（如果有）一起应用。第二种方法首先执行范围扫描，然后对结果元组进行分组。

-   [松散索引扫描](group-by-optimization.html#loose-index-scan)

-   [紧密索引扫描](group-by-optimization.html#tight-index-scan)

    松散索引扫描也可以在没有的情况下使用`通过...分组`在某些条件下。看[跳过扫描范围访问方法](range-optimization.html#range-access-skip-scan).

##### 松散索引扫描

[](<>)

最有效的处理方式`通过...分组`是使用索引直接检索分组列的时候。通过这种访问方式，MySQL 使用了一些索引类型的属性，即键是有序的（例如，`BTREE`）。此属性允许在索引中使用查找组，而无需考虑索引中满足所有条件的所有键`在哪里`状况。这种访问方法只考虑索引中的一小部分键，因此称为松散索引扫描。没有的时候`在哪里`子句中，松散索引扫描读取与组数一样多的键，这可能比所有键的数量小得多。如果`在哪里`子句包含范围谓词（参见[`范围`](explain-output.html#jointype_range)加入输入[第 8.8.1 节，“使用 EXPLAIN 优化查询”](using-explain.html))，松散索引扫描查找满足范围条件的每个组的第一个键，并再次读取尽可能少的键数。这在以下条件下是可能的：

-   查询针对单个表。

-   这`通过...分组`仅命名构成索引最左侧前缀的列，而不命名其他列。（如果，而不是`通过...分组`，查询有一个`清楚的`子句中，所有不同的属性指的是构成索引最左前缀的列。）例如，如果一个表`t1`有一个索引`(c1,c2,c3)`, 如果查询有，则松散索引扫描适用`按 c1、c2 分组`.如果查询有，则不适用`按 c2、c3 分组`（列不是最左边的前缀）或`按 c1、c2、c4 分组`(`c4`不在索引中）。

-   选择列表（如果有）中使用的唯一聚合函数是[`最小（）`](aggregate-functions.html#function_min)和[`最大限度（）`](aggregate-functions.html#function_max)，并且它们都引用同一列。该列必须在索引中，并且必须紧跟在索引中的列之后`通过...分组`.

-   指数的任何其他部分，而不是来自`通过...分组`查询中引用的必须是常量（也就是说，它们必须以与常量相等的方式引用），除了[`最小（）`](aggregate-functions.html#function_min)要么[`最大限度（）`](aggregate-functions.html#function_max)职能。

-   对于索引中的列，必须索引完整的列值，而不仅仅是前缀。例如，与`c1 VARCHAR(20), 索引 (c1(10))`，索引仅使用前缀`c1`值并且不能用于松散索引扫描。

    如果松散索引扫描适用于查询，则[`解释`](explain.html)输出显示`使用索引进行分组`在里面`额外的`柱子。

    假设有索引`idx(c1,c2,c3)`桌子上`t1(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;
```

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

-   除了聚合函数[`最小（）`](aggregate-functions.html#function_min)要么[`最大限度（）`](aggregate-functions.html#function_max)：

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

    查询是否包括`其中 c3 = *`常量`*`, 可以使用松散索引扫描。

    Loose Index Scan 访问方法可以应用于选择列表中的其他形式的聚合函数引用，除了[`最小（）`](aggregate-functions.html#function_min)和[`最大限度（）`](aggregate-functions.html#function_max)已经支持的参考：

-   [`平均（不同）`](aggregate-functions.html#function_avg),[`总和（不同）`](aggregate-functions.html#function_sum)， 和[`计数（不同）`](aggregate-functions.html#function_count)支持。[`平均（不同）`](aggregate-functions.html#function_avg)和[`总和（不同）`](aggregate-functions.html#function_sum)取一个论点。[`计数（不同）`](aggregate-functions.html#function_count)可以有多个列参数。

-   必须没有`通过...分组`要么`清楚的`查询中的子句。

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

    假设有索引`idx(c1,c2,c3)`桌子上`t1(c1,c2,c3,c4)`.松散索引扫描访问方法可用于以下查询：


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

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

##### 紧密索引扫描

Tight Index Scan 可以是全索引扫描，也可以是范围索引扫描，具体取决于查询条件。

当 Loose Index Scan 的条件不满足时，仍然可以避免为`通过...分组`查询。如果存在范围条件`在哪里`子句，该方法只读取满足这些条件的键。否则，它将执行索引扫描。因为此方法读取由定义的每个范围内的所有键`在哪里`子句，如果没有范围条件则扫描整个索引，称为紧索引扫描。使用紧密索引扫描，只有在找到所有满足范围条件的键之后才执行分组操作。

要使此方法起作用，查询中的所有列有一个恒定的相等条件就足够了，这些列引用了在部分之前或之间的部分键`通过...分组`钥匙。来自相等条件的常量填充搜索键中的任何“空白”，以便可以形成索引的完整前缀。然后这些索引前缀可用于索引查找。如果`通过...分组`结果需要排序，并且可以形成作为索引前缀的搜索键，MySQL也避免了额外的排序操作，因为在有序索引中使用前缀搜索已经按顺序检索了所有键。

假设有索引`idx(c1,c2,c3)`桌子上`t1(c1,c2,c3,c4)`.以下查询不适用于前面描述的松散索引扫描访问方法，但仍适用于紧密索引扫描访问方法。

-   有一个缺口`通过...分组`，但它被条件覆盖`c2 ='一个'`：

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