J'ai une requête qui prend un temps particulièrement long à exécuter (15+ secondes) et elle ne fait qu'empirer avec le temps à mesure que mon ensemble de données se développe. J'ai optimisé cela dans le passé, et j'ai ajouté des indices, un tri au niveau du code et d'autres optimisations, mais il a besoin d'être affiné.
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 donner le son sound id
et la note moyenne des sons les plus récents et sortis. Il y a environ 1500 sons et 2 millions de notes.
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 la 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 une fois obtenus, les performances du site ne sont donc pas vraiment un problème, mais mes cache-cache prennent de plus en plus de temps à s'exécuter en raison de la longueur de cet appel, et cela commence à devenir un problème. Cela ne semble pas beaucoup de chiffres à croquer dans une seule requête…
Que puis-je faire de plus pour améliorer les performances ?
la source
EXPLAIN
sortie?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
Réponses:
Après avoir examiné la requête, les tables et les clauses WHERE AND GROUP BY, je recommande ce qui suit:
Recommandation n ° 1) Refactoriser la requête
J'ai réorganisé la requête pour faire trois (3) choses:
Voici ma requête proposée:
Recommandation n ° 2) Indexer la table des sons avec un index qui tiendra compte de la clause WHERE
Les colonnes de cet index incluent toutes les colonnes de la clause WHERE avec les valeurs statiques en premier et la cible en mouvement en dernier
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 rateable_id. Garçon, je me sens stupide !!!
MISE À JOUR 2011-05-21 19:20
Peut-être que faire l'index sera suffisant pour améliorer les choses.
MISE À JOUR 2011-05-21 22:56
Veuillez exécuter ceci:
MISE À JOUR 2011-05-21 23:34
Je l'ai refactorisé à nouveau. Essayez celui-ci s'il vous plaît:
MISE À JOUR 2011-05-21 23:55
Je l'ai refactorisé à nouveau. Essayez celui-ci s'il vous plaît (dernière fois):
MISE À JOUR 2011-05-22 00:12
Je déteste abandonner !!!!
MISE À JOUR 2011-05-22 07:51
Cela me dérange que les notes reviennent avec 2 millions de lignes dans EXPLAIN. Ensuite, ça m'a frappé. Vous pourriez avoir besoin d'un autre index sur le tableau des notes qui commence par rateable_type:
Le but de cet indice est de réduire la table temporaire qui manipule les notes afin qu'elle soit inférieure à 2 millions. Si nous pouvons réduire considérablement la taille de cette table temporaire (au moins la moitié), nous pouvons avoir un meilleur espoir dans votre requête et la mienne fonctionner plus rapidement également.
Après avoir créé cet index, veuillez réessayer ma requête initiale proposée et essayez également la vôtre:
MISE À JOUR 2011-05-22 18:39: MOTS FINAUX
J'avais refactorisé une requête dans une procédure stockée et ajouté un index pour aider à répondre à une question sur l'accélération des choses. J'ai reçu 6 votes positifs, la réponse a été acceptée et j'ai reçu 200 primes.
J'avais également refactorisé une autre requête (résultats marginaux) et ajouté un index (résultats spectaculaires). J'ai reçu 2 votes positifs et j'ai accepté la réponse.
J'ai ajouté un index pour un autre défi de requête et j'ai été voté une fois
et maintenant votre question .
Le fait de vouloir répondre à toutes les questions comme celles-ci (y compris la vôtre) a été inspiré par une vidéo YouTube que j'ai regardée lors de la refactorisation des requêtes.
Merci encore, @coneybeare !!! Je voulais répondre à cette question dans toute la mesure du possible, pas seulement accepter des points ou des distinctions. Maintenant, je sens que j'ai gagné des points !!!
la source
Merci pour la sortie EXPLAIN. Comme vous pouvez le constater à partir de cette déclaration, la raison pour laquelle cela prend si longtemps est le tableau complet du tableau des notes. Rien dans l'instruction WHERE ne filtre les 2 millions de lignes.
Vous pouvez ajouter un index sur ratings.type, mais je suppose que la CARDINALITÉ va être vraiment faible et que vous continuerez à scanner pas mal de lignes
ratings
.Alternativement, vous pouvez essayer d'utiliser des indices pour forcer mysql à utiliser les index des sons.
Mise à jour:
Si c'était moi, j'ajouterais un index
sounds.created
car cela a la meilleure chance de filtrer les lignes et forcera probablement l'optimiseur de requête mysql à utiliser les index de la table des sons. Méfiez-vous des requêtes qui utilisent de longues périodes de temps créées (1 an, 3 mois, cela dépend juste de la taille de la table des sons).la source
Si cela doit être une requête disponible "à la volée" , cela limite un peu vos options.
Je vais suggérer de diviser pour mieux régler ce problème.
la source
sounds
,ratings
à la requête du milieu), mais il a verrouillé ma boîte sql et j'ai dû tuer le processus.Utilisez des JOIN, pas des sous-requêtes. Une de vos tentatives de sous-requête a-t-elle aidé?
AFFICHER CRÉER TABLE sons \ G
AFFICHER CRÉER LE TABLEAU \ G
Il est souvent avantageux d'avoir des index "composés", pas des index à colonne unique. Peut-être INDEX (type, created_at)
Vous filtrez sur les deux tables dans un JOIN; cela risque d'être un problème de performances.
Vous recommandons d'avoir un identifiant auto_increment
ratings
, de créer un tableau récapitulatif et d'utiliser l'identifiant AI pour garder une trace de l'endroit où vous vous êtes «arrêté». Cependant, ne stockez pas de moyennes dans un tableau récapitulatif:Au lieu de cela, conservez le SUM (ratings.rating). La moyenne des moyennes est mathématiquement incorrecte pour calculer une moyenne; (somme des sommes) / (somme des comptes) est correct.
la source