Як я можу далі оптимізувати цей запит MySQL?


9

У мене є запит, який потребує особливо тривалого часу (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
Дерек Дауні

@coneybeare Це був дуже цікавий виклик для мене сьогодні !!! +1 для вас питання. Я б хотів, щоб у майбутньому виникло більше таких питань.
RolandoMySQLDBA

@coneybeare Схоже, що новий EXPLAIN читає лише 21540 рядків (359 X 60) замість 2 008 306. Будь ласка, запустіть ПОЯСНЕННЯ на запит, який я спочатку запропонував у своїй відповіді. Я хотів би побачити кількість рядків, що виходять із цього.
RolandoMySQLDBA

@RolandoMySQLDBA Нове пояснення справді показує, що менша кількість рядків з індексом, однак час виконання запиту все ще було приблизно 15 секунд, не показуючи жодних покращень
coneybeare

@coneybeare Я добре налаштував запит. Будь ласка, запустіть ПОЯСНЕННЯ на мій новий запит. Я додав це до своєї відповіді.
RolandoMySQLDBA

Відповіді:


7

Переглянувши запит, таблиці та пункти WHERE AND GROUP BY, я рекомендую наступне:

Рекомендація № 1) Рефактор запиту

Я реорганізував запит, щоб зробити три (3) речі:

  1. створювати менші темп-таблиці
  2. Обробіть пункт WHERE на цих темп-таблицях
  3. Затримка приєднання до останнього

Ось мій запропонований запит:

SELECT
  sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);

Рекомендація № 2) Проіндексуйте таблицю звуків за допомогою індексу, який міститиме пропозицію WHERE

Стовпці цього індексу містять усі стовпці із пункту WHERE зі статичними значеннями першими та останніми рухомими цілями

ALTER TABLE sounds ADD INDEX support_index
(blacklisted,ready_for_deployment,deployed,type,created_at);

Я щиро вірю, що ви будете приємно здивовані. Спробувати !!!

ОНОВЛЕННЯ 2011-05-21 19:04

Я щойно побачив кардинальність. ТАК !!! Кардинальність 1 для rateable_id. Хлопчик, я почуваюся дурним !!!

ОНОВЛЕННЯ 2011-05-21 19:20

Можливо, складання індексу буде достатньо для покращення речей.

ОНОВЛЕННЯ 2011-05-21 22:56

Будь ласка, запустіть це:

EXPLAIN SELECT
  sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes FROM
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);

ОНОВЛЕННЯ 2011-05-21 23:34

Я знову його відремонтував. Спробуйте це, будь ласка:

EXPLAIN
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes FROM
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
;

ОНОВЛЕННЯ 2011-05-21 23:55

Я знову його відремонтував. Спробуйте це будь-ласка (востаннє):

EXPLAIN
  SELECT A.id,avg(B.rating) AS avg_rating, count(B.rating) AS votes FROM
  (
    SELECT BB.* FROM
    (
      SELECT id FROM sounds
      WHERE blacklisted = false 
      AND   ready_for_deployment = true 
      AND   deployed = true 
      AND   type = "Sound" 
      AND   created_at > '2011-03-26 21:25:49'
    ) AA INNER JOIN sounds BB USING (id)
  ) A INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) B
  ON A.id = B.rateable_id
  GROUP BY B.rateable_id;

ОНОВЛЕННЯ 2011-05-22 00:12

Я ненавиджу здаватися !!!!

EXPLAIN
  SELECT A.*,avg(B.rating) AS avg_rating, count(B.rating) AS votes FROM
  (
    SELECT BB.* FROM
    (
      SELECT id FROM sounds
      WHERE blacklisted = false 
      AND   ready_for_deployment = true 
      AND   deployed = true 
      AND   type = "Sound" 
      AND   created_at > '2011-03-26 21:25:49'
    ) AA INNER JOIN sounds BB USING (id)
  ) A,
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
    AND AAA.rateable_id = A.id
  ) B
  GROUP BY B.rateable_id;

ОНОВЛЕННЯ 2011-05-22 07:51

Мене непокоїть, що рейтинги повертаються з 2 мільйонами рядків у ПОЯСНЕННІ. Потім мене це вдарило. Можливо, вам знадобиться інший індекс у таблиці оцінок, який починається з rateable_type:

ALTER TABLE ratings ADD INDEX
rateable_type_rateable_id_ndx (rateable_type,rateable_id);

Мета цього індексу - зменшити темп-таблицю, яка маніпулює рейтингами, щоб вона становила менше 2 мільйонів. Якщо ми можемо отримати цю таблицю темпів значно меншою (принаймні наполовину), то ми можемо мати більше надії у вашому запиті, і моя також працює швидше.

Створивши цей індекс, будь ласка, повторіть мій оригінальний запропонований запит, а також спробуйте свій:

SELECT
  sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
  SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes
  (
    SELECT id FROM sounds
    WHERE blacklisted = false 
    AND   ready_for_deployment = true 
    AND   deployed = true 
    AND   type = "Sound" 
    AND   created_at > '2011-03-26 21:25:49'
  ) AA INNER JOIN
  (
    SELECT AAA.ratings,AAA.rateable_id
    FROM ratings AAA
    WHERE rateable_type = 'Sound'
  ) BB
  ON AA.id = BB.rateable_id
  GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);

ОНОВЛЕННЯ 2011-05-22 18:39: ЗАКЛЮЧНІ СЛОВА

Я відновив запит у збереженій процедурі та додав індекс, щоб допомогти відповісти на запитання щодо прискорення роботи. Я отримав 6 результатів, прийняв відповідь і взяв 200 баунті.

Я також відновив ще один запит (граничні результати) і додав індекс (драматичні результати). Я отримав 2 оновлення, і відповідь була прийнята.

Я додав індекс для ще однієї задачі на запити, і був звернений один раз

а тепер ваше запитання .

Бажаючи відповісти на всі подібні питання (включаючи ваше), надихнуло відео YouTube, яке я переглянув на запити рефакторингу.

Дякую ще раз, @coneybeare !!! Я хотів відповісти на це питання якнайповніше, а не просто приймати бали чи похвалу. Тепер я можу відчути, що я заробив бали !!!


Я додав індекс, вчасно не покращившись. Ось новий ПОЯСНЕННЯ
coneybeare

ПОЯСНЕННЯ на запит із рекомендації 1: cloud.coneybeare.net/6xZ2 Для запуску цього запиту знадобилося близько 30 секунд
coneybeare

Мені довелося трохи відредагувати ваш синтаксис (я додав FROM перед першим запитом, і мені довелося позбутися псевдоніму AAA). Ось ПОЯСНЕННЯ : cloud.coneybeare.net/6xlq Дійсний запит запустив близько 30 секунд
coneybeare

@RolandoMySQLDBA: ПОЯСНУЙТЕ про оновлення о 23:55: cloud.coneybeare.net/6wrN Дійсний запит пробіг понад хвилину, тому я вбив процес
coneybeare

Другий внутрішній вибір не може отримати доступ до таблиці вибору A, тому A.id видає помилку.
coneybeare

3

Дякуємо за вихід EXPLAIN. Як ви можете зрозуміти з цього твердження, причина, яка займає так багато часу, - це повна таблицяможна на таблиці оцінок. Ніщо в заяві WHERE не фільтрує 2 мільйони рядків.

Ви можете додати індекс у rating.type, але я гадаю, що КАРДІНАЛЬНІСТЬ буде дійсно низьким, і ви все ще будете сканувати досить багато рядків ratings.

Крім того, ви можете спробувати використовувати підказки, щоб змусити mysql використовувати індекси звуку.

Оновлено:

Якби це я, я додав би індекс на sounds.createdтой, що має найкращі шанси фільтрувати рядки, і, ймовірно, змусить оптимізатор запитів mysql використовувати індекси таблиці звуків. Просто остерігайтесь запитів, які використовують давно створені часові рамки (1 рік, 3 місяці, просто залежить від розміру таблиці звуків).


Схоже, ваша пропозиція була примітна для @coneybeare. +1 також від мене.
RolandoMySQLDBA

Покажчик на створеному не голиться ніколи. Ось оновлений ПОЯСНЕННЯ. cloud.coneybeare.net/6xvc
coneybeare

2

Якщо це повинен бути доступний запит "на ходу" , це обмежує ваші варіанти.

Я збираюся запропонувати розділити і перемогти цю проблему.

--
-- Create an in-memory table
CREATE TEMPORARY TABLE rating_aggregates (
rateable_id INT,
avg_rating NUMERIC,
votes NUMERIC
);
--
-- For now, just aggregate. 
INSERT INTO rating_aggregates
SELECT ratings.rateable_id, 
avg(ratings.rating) AS avg_rating, 
count(ratings.rating) AS votes FROM `sounds`  
WHERE ratings.rateable_type = 'Sound' 
GROUP BY ratings.rateable_id;
--
-- Now get your final product --
SELECT 
sounds.*, 
rating_aggregates.avg_rating, 
rating_aggregates.votes AS votes,
rating_aggregates.rateable_id 
FROM rating_aggregates 
INNER JOIN sounds ON (sounds.id = rating_aggregates.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";

здається, @coneybeare побачив щось у вашій пропозиції. +1 від мене !!!
RolandoMySQLDBA

Я насправді не міг змусити це працювати. Я отримував sql помилки, що я не знав, як підходити. Я ніколи не працював із тимчасовими таблицями
coneybeare

Я отримав це в кінцевому підсумку (мені довелося додати ВІД sounds, ratingsдо середнього запиту), але це заблокувало моє поле sql і мені довелося вбити процес.
coneybeare

0

Використовуйте ПРИЄДНАЙТЕСЬ, а не підзапити. Чи допомогла будь-яка з ваших спроб запиту?

ПОКАЗУЙТЕ СТВОРИТИ звуки ТАБЛИЦІ \ G

ПОКАЗУЙТЕ СТВОРИТИ рейтинги ТАБЛИЦІ \ G

Часто вигідно мати "складені" індекси, а не одноколонкові. Можливо, INDEX (тип, створений_at)

Ви фільтруєте обидві таблиці в ПРИЄДНАННІ; це може бути проблемою продуктивності.

Є близько 1500 звуків і 2 мільйони рейтингів.

Порекомендуйте увімкнути ідентифікатор auto_increment ratings, скласти підсумкову таблицю та використовувати ідентифікатор AI для відстеження того, де ви «зупинилися». Однак не зберігайте середні показники у підсумковій таблиці:

avg (ratings.rating) AS avg_rating,

Замість цього зберігайте СУМ (рейтинг. Оцінку). Середнє значення середніх значень математично невірно для обчислення середнього значення; (сума сум) / (сума підрахунків) правильна.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.