У меня есть запрос, который занимает особенно много времени (15+ секунд), и он только ухудшается со временем по мере роста моего набора данных. Я оптимизировал это в прошлом и добавил индексы, сортировку на уровне кода и другие оптимизации, но это требует некоторой дальнейшей доработки.
SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM `sounds`
INNER JOIN ratings ON sounds.id = ratings.rateable_id
WHERE (ratings.rateable_type = 'Sound'
AND sounds.blacklisted = false
AND sounds.ready_for_deployment = true
AND sounds.deployed = true
AND sounds.type = "Sound"
AND sounds.created_at > "2011-03-26 21:25:49")
GROUP BY ratings.rateable_id
Цель запроса - получить sound id
среднюю оценку самых последних выпущенных звуков. Есть около 1500 звуков и 2 миллиона оценок.
У меня есть несколько показателей sounds
mysql> show index from sounds;
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| sounds | 0 | PRIMARY | 1 | id | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 1 | deployed | A | 5 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 2 | ready_for_deployment | A | 12 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_name | 1 | name | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_description | 1 | description | A | 1388 | 128 | NULL | YES | BTREE | |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+
и несколько на ratings
mysql> show index from ratings;
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| ratings | 0 | PRIMARY | 1 | id | A | 2008251 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 1 | rateable_id | A | 18 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 2 | rating | A | 9297 | NULL | NULL | YES | BTREE | |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
Здесь EXPLAIN
mysql> EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id;
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| 1 | SIMPLE | ratings | index | index_ratings_on_rateable_id_and_rating | index_ratings_on_rateable_id_and_rating | 9 | NULL | 2008306 | Using where |
| 1 | SIMPLE | sounds | eq_ref | PRIMARY,sounds_ready_for_deployment_and_deployed | PRIMARY | 4 | redacted_production.ratings.rateable_id | 1 | Using where |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+
Я кеширую полученные результаты, так что производительность сайта не является большой проблемой, но мой подогрев кеша работает все дольше и дольше из-за того, что этот вызов занимает так много времени, и это начинает становиться проблемой. Это не похоже на большое количество цифр в одном запросе ...
Что еще я могу сделать, чтобы сделать это лучше ?
источник
EXPLAIN
выход?EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id
Ответы:
После просмотра запроса, таблиц и предложений WHERE AND GROUP BY я рекомендую следующее:
Рекомендация № 1) Рефакторинг запроса
Я реорганизовал запрос, чтобы сделать три (3) вещи:
Вот мой предложенный запрос:
Рекомендация № 2) Индексируйте таблицу звуков индексом, который будет соответствовать предложению WHERE.
Столбцы этого индекса включают все столбцы из предложения WHERE со статическими значениями в первую очередь и перемещением цели в последнюю очередь
Я искренне верю, что вы будете приятно удивлены. Попробуйте!
ОБНОВЛЕНИЕ 2011-05-21 19:04
Я только что увидел мощность. Ой! Количество элементов 1 для rateable_id. Мальчик, я чувствую себя глупо !!!
ОБНОВЛЕНИЕ 2011-05-21 19:20
Возможно, создание индекса будет достаточно, чтобы улучшить положение вещей.
ОБНОВЛЕНИЕ 2011-05-21 22:56
Пожалуйста, запустите это:
ОБНОВЛЕНИЕ 2011-05-21 23:34
Я рефакторинг это снова. Попробуйте это пожалуйста:
ОБНОВЛЕНИЕ 2011-05-21 23:55
Я рефакторинг это снова. Попробуйте это пожалуйста (в последний раз):
ОБНОВЛЕНИЕ 2011-05-22 00:12
Я ненавижу сдаваться !!!!
ОБНОВЛЕНИЕ 2011-05-22 07:51
Меня беспокоит, что рейтинги возвращаются с 2 миллионами строк в ОБЪЯСНЕНИИ. Затем он ударил меня. Вам может понадобиться другой индекс в таблице рейтингов, который начинается с rateable_type:
Цель этого индекса - уменьшить временную таблицу, которая манипулирует рейтингами, чтобы она составляла менее 2 миллионов. Если мы сможем значительно уменьшить эту временную таблицу (по крайней мере, наполовину), у нас будет больше надежды на ваш запрос, и мой будет работать быстрее.
После создания этого индекса, пожалуйста, повторите мой исходный предложенный запрос, а также попробуйте свой:
ОБНОВЛЕНИЕ 2011-05-22 18:39: ЗАКЛЮЧИТЕЛЬНЫЕ СЛОВА
Я реорганизовал запрос в хранимой процедуре и добавил индекс, чтобы помочь ответить на вопрос об ускорении процесса. Я получил 6 голосов, принял ответ и получил награду в размере 200.
Я также реорганизовал другой запрос (маргинальные результаты) и добавил индекс (впечатляющие результаты). Я получил 2 отзыва и получил ответ.
Я добавил индекс для еще одного запроса запроса и один раз проголосовал
а теперь твой вопрос .
Желание ответить на все подобные вопросы (включая ваши) было вдохновлено видео на YouTube, которое я посмотрел о рефакторинге запросов.
Еще раз спасибо, @coneybeare !!! Я хотел ответить на этот вопрос в максимально возможной степени, а не просто принимать баллы или почести. Теперь я чувствую, что заработал очки !!!
источник
Спасибо за выход EXPLAIN. Как вы можете сказать из этого заявления, причина, по которой это занимает так много времени, заключается в полном сканировании таблицы в таблице рейтингов. Ничто в операторе WHERE не фильтрует 2 миллиона строк.
Вы можете добавить индекс для ratings.type, но я предполагаю, что CARDINALITY будет очень низким, и вы все равно будете сканировать довольно много строк
ratings
.В качестве альтернативы вы можете попытаться использовать индексные подсказки, чтобы заставить mysql использовать индексы звуков.
Обновлено:
Если бы это был я, я бы добавил индекс, так
sounds.created
как он лучше всего фильтрует строки и, вероятно, заставит оптимизатор запросов mysql использовать индексы таблицы звуков. Остерегайтесь запросов, которые используют давно созданные временные рамки (1 год, 3 месяца, зависит только от размера таблицы звуков).источник
Если это должен быть доступный запрос «на лету» , то это немного ограничивает ваши возможности.
Я собираюсь предложить разделяй и властвуй для этой проблемы.
источник
sounds
,ratings
к среднему запросу), но он заблокировал мой sql box, и мне пришлось убить процесс.Используйте СОЕДИНЕНИЯ, а не подзапросы. Помогла ли какая-нибудь из ваших попыток подзапроса?
ПОКАЗАТЬ СОЗДАТЬ ТАБЛИЦУ звуков \ G
ПОКАЗАТЬ CREATE TABLE рейтинги \ G
Часто полезно иметь «составные» индексы, а не одностолбчатые. Возможно, ИНДЕКС (тип, create_at)
Вы фильтруете обе таблицы в JOIN; это может быть проблемой производительности.
Рекомендую, чтобы у вас был идентификатор auto_increment
ratings
, создать сводную таблицу и использовать идентификатор AI для отслеживания того, где вы «остановились». Однако не храните средние значения в сводной таблице:Вместо этого сохраните СУММУ (рейтинг. Рейтинг). Среднее значение среднего математически неверно для вычисления среднего значения; (сумма сумм) / (сумма отсчетов) верна.
источник