我正在使用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。
答案 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子句可以轻松解释您看到的性能大幅下降。
满足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
(需要处理所有数据),那么性能将会下降。