满足GROUP BY
子句的最一般方法是扫描整个表并创建一个新的临时表,其中每个组的所有行都是连续的,然后使用此临时表来发现组并应用聚合函数(如果有)。在某些情况下,MySQL 能够做得比这更好,并通过使用索引访问避免创建临时表。
使用索引的最重要的先决条件
GROUP BY
是所有GROUP
BY
列都引用来自同一索引的属性,并且索引按顺序存储其键(例如,对于BTREE
索引,但不是
HASH
索引)。临时表的使用是否可以被索引访问取代还取决于查询中使用了索引的哪些部分、为这些部分指定的条件以及所选的聚合函数。
有两种方法可以GROUP BY
通过索引访问执行查询,如以下部分所述。第一种方法将分组操作与所有范围谓词(如果有)一起应用。第二种方法首先执行范围扫描,然后对生成的元组进行分组。
在 MySQL 中,GROUP BY
用于排序,因此服务器也可以ORDER BY
对分组进行优化。但是,不推荐依赖隐式或显式GROUP BY
排序。请参阅第 8.2.1.14 节,“ORDER BY 优化”。
最有效的处理方式GROUP
BY
是使用索引直接检索分组列。使用这种访问方法,MySQL 使用某些索引类型的属性,这些索引类型的键是有序的(例如,BTREE
)。此属性允许在索引中使用查找组,而无需考虑索引中满足所有
WHERE
条件的所有键。这种访问方法只考虑索引中的一小部分键,因此称为松散索引扫描。当没有WHERE
子句时,松散索引扫描读取与组数一样多的键,这可能比所有键的数量小得多。如果WHERE
子句包含范围谓词(请参阅
第 8.8.1 节“使用 EXPLAIN 优化查询”range
中对连接类型
的讨论),松散索引扫描查找满足范围条件的每个组的第一个键,并再次读取可能的最小值键的数量。这在以下条件下是可能的:
查询是针对单个表的。
GROUP BY
仅命名构成索引最左边前缀的列,不命名其他列 。(如果不是GROUP BY
,查询有一个DISTINCT
子句,所有不同的属性都引用构成索引最左边前缀的列。)例如,如果表t1
有一个索引(c1,c2,c3)
,如果查询有 ,松散索引扫描适用GROUP BY c1, c2
。如果查询具有GROUP BY c2, c3
(列不是最左边的前缀)或GROUP BY c1, c2, c4
(c4
不在索引中),则不适用。选择列表中唯一使用的聚合函数(如果有的话)是
MIN()
和MAX()
,它们都引用同一列。该列必须在索引中,并且必须紧跟在GROUP BY
.除了查询中引用的索引部分之外,索引的任何其他部分都
GROUP BY
必须是常量(即,它们必须与常量等同地引用),MIN()
或MAX()
函数的参数除外。对于索引中的列,必须索引完整的列值,而不仅仅是前缀。例如,对于
c1 VARCHAR(20), INDEX (c1(10))
,索引仅使用值的前缀,c1
不能用于松散索引扫描。
如果松散索引扫描适用于查询,则
EXPLAIN
输出显示
Using index for group-by
在
Extra
列中。
假设
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;
由于给出的原因,无法使用此快速选择方法执行以下查询:
Loose Index Scan 访问方法可以应用于选择列表中的其他形式的聚合函数引用,除了已经支持
的MIN()
和
引用:MAX()
AVG(DISTINCT)
,SUM(DISTINCT)
, 和COUNT(DISTINCT)
受支持。AVG(DISTINCT)
并SUM(DISTINCT)
接受一个论点。COUNT(DISTINCT)
可以有多个列参数。查询中不能有
GROUP BY
orDISTINCT
子句。前面描述的松散索引扫描限制仍然适用。
假设
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;
紧密索引扫描可以是全索引扫描,也可以是范围索引扫描,这取决于查询条件。
When the conditions for a Loose Index Scan are not met, it
still may be possible to avoid creation of temporary tables
for GROUP BY
queries. If there are range
conditions in the WHERE
clause, this
method reads only the keys that satisfy these conditions.
Otherwise, it performs an index scan. Because this method
reads all keys in each range defined by the
WHERE
clause, or scans the whole index if
there are no range conditions, it is called a
Tight Index Scan. With
a Tight Index Scan, the grouping operation is performed only
after all keys that satisfy the range conditions have been
found.
For this method to work, it is sufficient that there be a
constant equality condition for all columns in a query
referring to parts of the key coming before or in between
parts of the GROUP BY
key. The constants
from the equality conditions fill in any “gaps”
in the search keys so that it is possible to form complete
prefixes of the index. These index prefixes then can be used
for index lookups. If the GROUP BY
result
requires sorting, and it is possible to form search keys
that are prefixes of the index, MySQL also avoids extra
sorting operations because searching with prefixes in an
ordered index already retrieves all the keys in order.
假设
idx(c1,c2,c3)
表
上有一个索引t1(c1,c2,c3,c4)
。以下查询不适用于前面描述的松散索引扫描访问方法,但仍适用于紧密索引扫描访问方法。
中存在间隙
GROUP BY
,但被条件覆盖c2 = 'a'
:SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
不以密钥的
GROUP BY
第一部分开始,但有一个条件为该部分提供常量:SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;