将GROUP BY添加到简单查询中会使速度降低1000

时间:2019-01-20 16:10:31

标签: mysql sql

我正在使用https://github.com/datacharmer/test_db中的测试数据库。它的大小适中,为160 Mb。要运行查询,我使用MySQL Workbench。

以下代码在0.015秒内运行

SELECT *
FROM employees INNER JOIN salaries ON employees.emp_no = salaries.emp_no

添加了GROUP BY的类似代码运行了15.0秒

SELECT AVG(salary), gender
FROM employees INNER JOIN salaries ON employees.emp_no = salaries.emp_no
GROUP BY gender

我检查了两个查询的执行计划,发现在两种情况下查询成本都是相似的,大约为60万。我应该补充一点,雇员表有30万行,薪水表大约有300万行。

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

问题解决方案:正如我发现的那样,由于评论和回答,这个问题与我有关,而没有注意到在第一次查询的情况下,我的IDE将结果限制为1000行。那就是我得到0.015s的方式。实际上,在我的情况下,加入联接需要10.0s。如果创建了性别索引(此数据库中已经存在employee.emp_no和salaries.emp_no的索引),则需要10.0s的时间进行加入和分组。没有性别索引,第二次查询需要18.0s。

3 个答案:

答案 0 :(得分:2)

第一个查询的EXPLAIN显示它对type=ALL中的30万行进行了表扫描(employees),并且对每个查询都做了部分主键(type=ref )在salaries中查找到1行(估计)。

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                            |
+----+-------------+-----------+------+---------------+---------+---------+----------------------------+--------+---------------------------------+

在“额外”字段中看到Using temporary; Using filesort吗?这意味着查询必须建立一个临时表来累积每个组的AVG()结果。它必须使用一个临时表,因为MySQL不知道它将一起扫描每个性别的所有行,因此它必须假定它在扫描行时需要独立维护运行总计。跟踪两个(在这种情况下)性别总数似乎不是一个大问题,但是假设它是邮政编码或类似的东西?

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

如果我们可以创建按性别排序的索引,则MySQL的优化程序将知道它可以一起扫描所有具有相同性别的行。因此,它可以一次计算一个性别的跑步总数,然后在扫描完一个性别后,计算AVG(薪水),然后保证不再扫描该性别的更多行。因此,它可以跳过建立临时表的过程。

该索引有助于:

mysql> alter table employees add index (gender, emp_no);

现在,相同查询的EXPLAIN显示将进行索引扫描(type=index),该扫描访问相同数量的条目,但是它将以更有用的顺序进行扫描,以计算总AVG ()。

相同的查询,但没有Using temporary注释:

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)

答案 1 :(得分:1)

添加GROUP BY子句可以轻松解释您看到的性能大幅下降。

来自the documentation

  

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

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

如果您不需要汇总功能,请不要分组。如果这样做,请确保有单个索引引用文档中建议的所有分组列:

  

在某些情况下,MySQL可以做得更好,并且可以避免使用索引访问来创建临时表。

PS:请注意,自MySQL 5.7.5起,不支持类似SELECT…GROUP BY的语句(除非您关闭了ONLY_FULL_GROUP_BY选项)

答案 2 :(得分:0)

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

带有GROUP BY的第二个查询需要处理 all 数据以得出结果。

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