Добавление GROUP BY к простому запросу делает его медленнее на 1000 - PullRequest
0 голосов
/ 20 января 2019

Я использую тестовую БД из https://github.com/datacharmer/test_db. Имеет умеренный размер 160 Мб.Для выполнения запросов я использую 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

Iпроверил план выполнения для обоих запросов и обнаружил, что в обоих случаях стоимость запроса одинакова и составляет около 600 тыс.Я должен добавить, что таблица сотрудника имеет 300 тыс. Строк, а таблица зарплаты - около 3 млн. Строк.

Кто-нибудь может подсказать причину, по которой разница во времени выполнения настолько велика?Мне нужно это объяснение, чтобы понять, как лучше работает SQL.

Решение проблемы: Как я выяснил из-за комментариев и ответов, проблема была связана со мной, не замечающим, что в случае первого запроса моя IDE ограничивала результатдо 1000 рядов.Вот так я и получил 0,015 с.На самом деле, в моем случае требуется 10.0 секунд.Если индекс для пола создан (индексы для employee.emp_no и salaries.emp_no уже существуют в этой БД), потребуется 10.0 с, чтобы объединиться и сгруппировать.Без индекса для пола второй запрос занимает 18 секунд.

Ответы [ 3 ]

0 голосов
/ 20 января 2019

EXPLAIN для первого запроса показывает, что он выполняет сканирование таблицы (type=ALL) из 300 тыс. Строк из employees, а для каждого из них выполняет поиск частичного первичного ключа (type=ref) до 1 строки ( по оценкам) в salaries.

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 в поле Extra? Это означает, что запрос должен создать временную таблицу для накопления результатов 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)
0 голосов
/ 20 января 2019

Есть и другая причина, а также то, на что указывает GMB.По сути, вы, вероятно, смотрите на время первого запроса, пока он не вернет строку first .Я сомневаюсь, что он возвращает все строк за 0,015 секунды.

Второй запрос с GROUP BY должен обработать все данных для получения результатов.

Если вы добавите ORDER BY (который требует обработки всех данных) к первому запросу, то вы увидите аналогичное снижение производительности.

0 голосов
/ 20 января 2019

Добавление предложения GROUP BY может легко объяснить значительное снижение производительности, которое вы видите.

С документация :

Самый общий способ выполнить предложение GROUP BY - это просмотреть всю таблицу и создать новую временную таблицу, в которой все строки из каждой группы являются последовательными, а затем использовать эту временную таблицу для обнаружения групп и применения агрегатных функций (если есть). ).

Дополнительные расходы, связанные с процессом группировки, могут быть очень дорогими. Также группировка происходит, даже если агрегатная функция не используется.

Если вам не нужна агрегатная функция, не группируйте. Если вы это сделаете, убедитесь, что у вас есть один индекс, который ссылается на все сгруппированные столбцы, как указано в документации:

В некоторых случаях MySQL может работать намного лучше и избегать создания временных таблиц с помощью доступа по индексу.

PS: обратите внимание, что подобные «SELECT * ... GROUP BY» операторы не поддерживаются начиная с MySQL 5.7.5 (если вы не отключите опцию ONLY_FULL_GROUP_BY)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...