Mysql 将GROUPBY添加到简单查询中会使查询速度减慢1000

Mysql 将GROUPBY添加到简单查询中会使查询速度减慢1000,mysql,sql,Mysql,Sql,我正在使用来自的测试数据库。它的中等大小为160MB。为了运行查询,我使用MySQL工作台 以下代码在0.015秒内运行 SELECT * FROM employees INNER JOIN salaries ON employees.emp_no = salaries.emp_no 添加了GROUP BY的类似代码将运行15.0s SELECT AVG(salary), gender FROM employees INNER JOIN salaries ON employees.emp_no

我正在使用来自的测试数据库。它的中等大小为160MB。为了运行查询,我使用MySQL工作台

以下代码在0.015秒内运行

SELECT *
FROM employees INNER JOIN salaries ON employees.emp_no = salaries.emp_no
添加了GROUP BY的类似代码将运行15.0s

SELECT AVG(salary), gender
FROM employees INNER JOIN salaries ON employees.emp_no = salaries.emp_no
GROUP BY gender
我检查了这两个查询的执行计划,发现在这两种情况下,查询成本相似,大约为600k。我应该补充一点,employee表有30万行,salary表大约有300万行

有人能提出为什么执行时间相差如此之大的原因吗?我需要这个解释来更好地理解SQL的工作方式


问题解决方案:由于注释和答案,我发现问题与我没有注意到在第一个查询中,我的IDE将结果限制为1000行有关。这就是我得到0.015秒的原因。实际上,在我的案例中,连接需要10.0秒。如果创建了性别索引(此数据库中已存在employees.emp_no和salaries.emp_no索引),则需要10.0秒才能创建加入和分组依据。没有性别索引,第二次查询需要18.0秒。

添加GROUP BY子句可以很容易地解释您看到的性能大幅下降

发件人:

满足GROUPBY子句的最常用方法是扫描整个表并创建一个新的临时表,其中每个组中的所有行都是连续的,然后使用此临时表来发现组并应用聚合函数(如果有)

分组过程产生的额外成本可能非常昂贵。此外,即使没有使用聚合函数,也会进行分组

如果不需要聚合函数,请不要分组。如果这样做,请确保有一个引用所有分组列的索引,如文档所建议的:

在某些情况下,MySQL能够做得更好,并通过使用索引访问避免创建临时表


注:请注意«选择*。。。自MySQL 5.7.5以来,不支持GROUP BY»-like语句(除非您仅关闭选项\u FULL\u GROUP BY)

第一个查询的解释显示,它对
员工的300K行进行表扫描(
type=ALL
),并对每一行进行部分主键(
type=ref
)查找(估计)在
工资中

mysql> explain SELECT * FROM employees 
  INNER JOIN salaries ON employees.emp_no = salaries.emp_no;
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+-------+
| id | select_type | table     | type | possible_keys | key     | key_len | ref                        | rows   | Extra |
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+-------+
|  1 | SIMPLE      | employees | ALL  | PRIMARY       | NULL    | NULL    | NULL                       | 299113 | NULL  |
|  1 | SIMPLE      | salaries  | ref  | PRIMARY       | PRIMARY | 4       | employees.employees.emp_no |      1 | NULL  |
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+-------+
第二个查询的解释(正如您在评论中提到的,实际上是计算AVG()的合理查询)显示了一些额外的内容:

mysql> EXPLAIN SELECT employees.gender, AVG(salary) FROM employees 
  INNER JOIN salaries ON employees.emp_no = salaries.emp_no 
  GROUP BY employees.gender;
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+---------------------------------+
| id | select_type | table     | type | possible_keys | key     | key_len | ref                        | rows   | Extra                           |
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+---------------------------------+
|  1 | SIMPLE      | employees | ALL  | PRIMARY       | NULL    | NULL    | NULL                       | 299113 | Using temporary; Using filesort |
|  1 | SIMPLE      | salaries  | ref  | PRIMARY       | PRIMARY | 4       | employees.employees.emp_no |      1 | NULL                            |
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+---------------------------------+
参见使用临时文件的
;在额外字段中使用文件排序
?这意味着查询必须构建一个临时表来累积每个组的AVG()结果。它必须使用临时表,因为MySQL不知道它将同时扫描每个性别的所有行,所以它必须假设在扫描行时需要独立地维护运行总计。跟踪两个(在本例中)性别总数似乎不是什么大问题,但假设是邮政编码之类的

创建临时表是一项非常昂贵的操作。这意味着写入数据,而不仅仅是像第一个查询那样读取数据

如果我们可以制作一个按性别排序的索引,那么MySQL的优化器就会知道它可以同时扫描所有具有相同性别的行。因此,它可以一次计算一个性别的运行总数,然后在扫描完一个性别后,计算平均值(工资),然后保证不会再扫描该性别的行。因此,它可以跳过构建临时表

此索引有助于:

mysql> alter table employees add index (gender, emp_no);
现在,对同一查询的解释表明,它将进行索引扫描(
type=index
),访问相同数量的条目,但它将以更有用的顺序扫描,以计算聚合平均值()

相同的查询,但没有使用临时的
,注意:

mysql> EXPLAIN SELECT employees.gender, AVG(salary) FROM employees 
  INNER JOIN salaries ON employees.emp_no = salaries.emp_no 
  GROUP BY employees.gender;
+----+-------------+-----------+-------+----------------+---------+---------+----------------------------+--------+-------------+
| id | select_type | table     | type  | possible_keys  | key     | key_len | ref                        | rows   | Extra       |
+----+-------------+-----------+-------+----------------+---------+---------+----------------------------+--------+-------------+
|  1 | SIMPLE      | employees | index | PRIMARY,gender | gender  | 5       | NULL                       | 299113 | Using index |
|  1 | SIMPLE      | salaries  | ref   | PRIMARY        | PRIMARY | 4       | employees.employees.emp_no |      1 | NULL        |
+----+-------------+-----------+-------+----------------+---------+---------+----------------------------+--------+-------------+
执行此查询的速度要快得多:

+--------+-------------+
| gender | AVG(salary) |
+--------+-------------+
| M      |  63838.1769 |
| F      |  63769.6032 |
+--------+-------------+
2 rows in set (1.06 sec)

还有另一个原因,正如GMB所指出的。基本上,您可能正在查看第一个查询的时间,直到它返回第一行。我怀疑它是否能在0.015秒内返回所有行

使用
分组依据的第二个查询需要处理所有数据以得出结果


如果您在第一个查询中添加了一个
ORDER BY(需要处理所有数据),那么您将看到类似的性能下降。

谢谢您的更正。标题应该是GROUP BY。您应该告诉我们您在这两个查询中的目标是什么。它们不是一回事,而且您的
groupby
查询使用了一种相当无效的语法,出于各种原因,MySQL支持这种语法。可能这里也有示例数据。为什么选择所有列,但只按性别分组,没有聚合函数(
SUM
COUNT
AVG
,等等)。你的目标是什么?不幸的是,MySQL支持这些无效的聚合查询,特别是对于SQL的新手来说。默认情况下,MySQL中的8设置为开,这将为您的第二次查询引发错误。第二次查询的目标是联接员工和薪资表,以便稍后查找每种性别的平均薪资。“第二次查询的目标是联接员工和薪资表,以便稍后查找每种性别的平均薪资。”?SQL支持
AVG()?或者正如GordonLinoff提到的,单元级查询以0.015秒的速度运行?OP在他/她的原始帖子中进行了编辑,但可能没有调整时间。好的,我试着用
忽略索引(性别)
执行,它在2.70秒内运行。在我的Macbook上。OP必须有一台非常旧或动力不足的计算机。第一个
SELECT*
查询没有分组依据,将在1.5秒内执行。在我的Macbook上。我不知道OP是如何在0.015秒内返回30万行的,除非他们只是错误地计算了100倍的时间。谢谢