it-swarm-fr.com

Comment puis-je optimiser cette requête MySQL plus loin?

J'ai une requête qui prend une période particulièrement longue pour exécuter (15 secondes) et que cela ne s'aggrave que de temps pendant que mon ensemble de données augmente. J'ai optimisé cela dans le passé et j'ai ajouté des indices, le tri de niveau de code et d'autres optimisations, mais il faut un affinage supplémentaire.

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

Le but de la requête est de me procurer le sound id 'S et la note moyenne des sons les plus récents et publiés. Il y a environ 1500 sons et 2 millions d'évaluations.

J'ai plusieurs indices sur 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      |         | 
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+

et plusieurs sur 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      |         | 
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

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

Je cache les résultats des résultats une fois obtenus, la performance du site n'est donc pas une grande partie d'un problème, mais mes chauffants de cache prennent plus de temps et plus à courir à cause de cet appel de la prise si longtemps, et cela commence à devenir un problème. Cela ne semble pas que beaucoup de chiffres soient crunch dans une requête ...

Que puis-je faire de plus pour que cela fonctionne mieux?

9
coneybeare

Après avoir examiné la requête, les tables et l'endroit où et le groupe par clauses, je recommande ce qui suit:

Recommandation n ° 1) refacteur la requête

J'ai réorganisé la requête pour faire trois (3) choses:

  1. créer des tables temporaires plus petites
  2. Traiter la clause WHERE sur ces tables TEMP
  3. Retard de jointure au dernier dernier

Voici ma requête proposée:

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);

Recommandation n ° 2) Indexez la table Sounds avec un index qui accueillera la clause WHERE

Les colonnes de cet indice incluent toutes les colonnes de la clause WHERE avec des valeurs statiques en premier et en déplaçant la cible en dernier

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

Je crois sincèrement que vous serez agréablement surpris. Essaie !!!

Mise à jour 2011-05-21 19:04

Je viens de voir la cardinalité. AIE !!! Cardinalité de 1 pour ratailable_id. Garçon, je me sens stupide !!!

Mise à jour 2011-05-21 19:20

Peut-être faire que l'index suffira à améliorer les choses.

Mise à jour 2011-05-21 22:56

S'il vous plaît courez ceci:

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);

Mise à jour 2011-05-21 23:34

Je l'ai refoulé à nouveau. Essayez celui-ci s'il vous plaît:

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
;

Mise à jour 2011-05-21 23:55

Je l'ai refoulé à nouveau. Essayez celui-ci s'il vous plaît (la dernière fois):

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;

Mise à jour 2011-05-22 00:12

Je déteste abandonner !!!!

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;

Mise à jour 2011-05-22 07:51

Cela m'a dérangé que les notes reviennent avec 2 millions de rangées dans l'explication. Ensuite, ça m'a frappé. Vous aurez peut-être besoin d'un autre index sur la table des évaluations qui commence par ratailable_type:

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

L'objectif de cet indice est de réduire la table Temp qui manipule les évaluations de manière à ce qu'elle soit inférieure à 2 millions. Si nous pouvons obtenir cette table temporaire de manière significative (au moins la moitié), nous pouvons alors avoir un meilleur espoir dans votre requête et que la mienne fonctionne également plus rapidement.

Après avoir effectué cet index, veuillez réessayer ma requête proposée originale et essayer également la vôtre:

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);

Mise à jour 2011-05-22 18:39: Mots finaux

J'avais refacturé une requête dans une procédure stockée et ajouté un index pour aider à répondre à une question à accélérer les choses. J'ai eu 6 upvotes, si la réponse a accepté et a pris une génération de 200 primes. .

J'avais également refactoré une autre requête (résultats marginaux) et ajouté un index (résultats dramatiques). J'ai eu 2 upvotes et si la réponse acceptée.

J'ai ajouté un index pour une autre requête de requête et a été avancé une fois

et maintenant votre question .

Voulant répondre à toutes les questions telles que celles-ci (y compris les vôtres) ont été inspirées par une vidéo YouTube, j'ai vue sur les requêtes de refactoring.

Merci encore, @ConeyBeare !!! Je voulais répondre à cette question dans la mesure du possible, non seulement des points ou des accolades. Maintenant, je peux sentir que j'ai gagné les points !!!

7
RolandoMySQLDBA

Merci pour la sortie expliquée. Comme vous pouvez le dire à cette déclaration, la raison pour laquelle il prend si longtemps est la totalité des tablettes sur la table des évaluations. Rien dans la relevé où l'énoncé filtre les rangées de 2Million.

Vous pourriez ajouter un index sur les notes.type, mais je suppose que la cardinalité va être réelle basse et vous numériserez toujours quelques lignes sur ratings.

Sinon, vous pouvez essayer d'utiliser Index Astuces pour forcer MySQL à utiliser les index de sons.

Mis à jour:

Si c'était moi, j'ajouterais un index sur sounds.created Comme cela a la meilleure chance de filtrer les lignes et forcera probablement l'optimiseur de requêtes MySQL à utiliser les index de la table Sounds. Attention à ce que des requêtes utilisent des cadres de temps créés de longue date (1 an, 3 mois dépend simplement de la taille de la table des sons).

3
Derek Downey

Si cela doit être un "On-The-Fly" Query disponible, alors cela limite un peu vos options.

Je vais suggérer de diviser et de conquérir pour ce problème.

--
-- 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";
2
randomx