J'ai un problème avec cette requête:
1 SIMPLE s const PRIMARY PRIMARY 4 const 1 Using index; Using temporary; Using filesort 1 SIMPLE str ref PRIMARY,ressort_id PRIMARY 4 const 13 Using index 1 SIMPLE atr ref PRIMARY,article_id PRIMARY 4 com.nps.lvz-prod.str.ressort_id 1262 Using index 1 SIMPLE a eq_ref PRIMARY PRIMARY 4 com.nps.lvz-prod.atr.article_id 1
Celle-ci est vraiment lente, elle prend parfois 14 secondes.
EXPLIQUEZ le montrer:
1 SIMPLE s const PRIMARY PRIMARY 4 const 1 Using index; Using temporary; Using filesort 1 SIMPLE str ref PRIMARY,ressort_id PRIMARY 4 const 1 Using index 1 SIMPLE atr ref PRIMARY,article_id PRIMARY 4 com.nps.lvz-prod.str.ressort_id 1262 Using index 1 SIMPLE a eq_ref PRIMARY PRIMARY 4 com.nps.lvz-prod.atr.article_id 1
donc le dernier type "tout" est vraiment mauvais. Mais j'ai déjà essayé de forcer l'utilisation de l'index, sans succès.
Le tableau des articles ressemble à ceci:
CREATE TABLE `article` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `node_id` varchar(255) NOT NULL DEFAULT '', `object_id` varchar(255) DEFAULT NULL, `headline_1` varchar(255) NOT NULL DEFAULT '', `created_at` datetime(3) NOT NULL, `updated_at` datetime(3) NOT NULL, `teaser_text` longtext NOT NULL, `content_text` longtext NOT NULL, PRIMARY KEY (`id`), KEY `article_nodeid` (`node_id`), KEY `article_objectid` (`object_id`), KEY `source_created` (`created_at`) ) ENGINE=InnoDB AUTO_INCREMENT=161116 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
Lorsque je supprime l'index FORCE, l'explication s'améliore, mais la requête est toujours lente.
Expliquer sans index de force:
1 SIMPLE s const PRIMARY PRIMARY 4 const 1 Using index; Using temporary; Using filesort 1 SIMPLE str ref PRIMARY,ressort_id PRIMARY 4 const 1 Using index 1 SIMPLE atr ref PRIMARY,article_id PRIMARY 4 com.nps.lvz-prod.str.ressort_id 1262 Using index 1 SIMPLE a ALL NULL NULL NULL NULL 146677 Using where; Using join buffer (flat, BNL join)
Et pour un autre smartressort id (3), cela ressemble à ceci:
SELECT a.* FROM smartressort AS s JOIN smartressort_to_ressort AS str ON s.id = str.smartressort_id JOIN article_to_ressort AS atr ON str.ressort_id = atr.ressort_id JOIN article AS a FORCE INDEX (source_created) ON atr.article_id = a.id WHERE s.id = 1 ORDER BY a.created_at DESC LIMIT 25;
Ici, nous avons 13 Ressorts pour un Smartressort. Lignes: 1x1x13x1262x1 = 16.406
1) Que puis-je faire pour accélérer cette demande?
2) Quel est le problème avec l'index source_created
? P >
5 Réponses :
Le SELECT *
que vous avez dans votre requête est moche, et cela peut souvent être un tueur d'index. Cela peut empêcher l'utilisation d'un index, car la plupart des index que vous définiriez ne couvriraient pas toutes les colonnes demandées par SELECT *
. L'approche de cette réponse est d'indexer toutes les autres tables de votre requête, ce qui inciterait donc MySQL à ne faire qu'une seule analyse de la table article
.
CREATE INDEX idx1 ON article_to_ressort (article_id, ressort_id); CREATE INDEX idx2 ON smartressort_to_ressort (ressort_id, smartressort_id);
Ces deux indices devraient accélérer le processus de jonction. Notez que je n'ai pas défini d'index pour la table smartressort
, en supposant que sa colonne id
est déjà une clé primaire. J'écrirais probablement votre requête en commençant par la table article
et en la rejoignant vers l'extérieur, mais cela ne devrait pas vraiment avoir d'importance.
De plus, forcer un index est généralement une mauvaise idée ou non nécessaire. L'optimiseur peut généralement déterminer quand il est préférable d'utiliser un index.
Notez que SELECT a. *
n'est pas si mal dans ce cas particulier, OP a besoin de toutes les données, donc la ligne doit être récupérée de toute façon - en général vous avez raison, il y a des cas où toutes les données pourraient être servi à partir d'un index - de la même manière que vos nouveaux index.
@gaborsch Eh bien, MySQL doit scanner quelque chose. S'il peut trouver un moyen efficace d'analyser la table article
, en gérant les jointures, alors il devrait le faire.
se mettre d'accord. Peut-être qu'un index sur article
, contenant id
et created_at
aiderait le ORDER BY
, évitant d'avoir à accéder à tous les lignes pour le tri?
Le fait est que l'index que vous décrivez ne couvrirait pas toutes les colonnes nécessaires à SELECT a. *
, il n'est donc pas clair si MySQL choisirait de l'utiliser.
CHOISIR beaucoup de colonnes DANS les tables ORDER PAR quelque chose LIMIT quelques
est un anti-modèle de performance notoire; il doit récupérer et ordonner tout un désordre de lignes et de colonnes, juste pour supprimer toutes les lignes sauf quelques lignes du jeu de résultats.
L'astuce consiste à déterminer les valeurs de article.id
dont vous avez besoin dans votre jeu de résultats, puis à récupérer uniquement ces valeurs. Cela s'appelle une jointure différée .
Cela devrait vous donner cet ensemble de valeurs id
. Il n'est probablement pas nécessaire de rejoindre la table smartressort
car smartressort_to_ressort
contient les valeurs id
dont vous avez besoin.
SELECT a.* FROM article a WHERE a.id IN ( SELECT a.id FROM article a JOIN article_to_ressort atr ON a.id = atr.article_id JOIN smartressort_to_ressort str ON atr.ressort_id = str.ressort_id WHERE str.smartressort_id = 1 ORDER BY a.created_at DESC LIMIT 25 ) ORDER BY a.created_at DESC
@Macx idéalement, l'optimiseur devrait d'abord accéder à la table a.created_at
parce que vous commandez par là-dessus, donc fondamentalement O.Jones oblige l'optimiseur MySQL à un meilleur plan ici .. MySQL 8.0 optimiserait automatiquement votre requête et éviterait "Utilisation temporaire" et fonctionnerait très bien
je ne peux pas utiliser la limite dans la sous-requête. Mais cela fonctionne et est beaucoup plus rapide: SELECT ar. * FROM article ar WHERE ar.id IN (SELECT atr.article_id from article_to_ressort atr JOIN smartressort_to_ressort str ON atr.ressort_id = str.ressort_id WHERE str.smartressort_id = 2) ORDER BY ar.created_at DESC LIMIT 25;
je dois également ajouter: CREATE INDEX idx3 ON article (id, created_at)
En plus de la bonne réponse de @TimBiegelsen, je recommanderais de modifier votre index source_created
:
... KEY `source_created` (`id`, `created_at`)
Le gain serait que MySQL pourrait l'utiliser pour tri, et n'aurait pas besoin de récupérer les 16406 lignes. Cela peut aider ou non, mais cela vaut la peine d'essayer (peut-être avec une déclaration explicite pour l'utiliser)
Pour commencer: vous pouvez supprimer la table smartressort
de votre requête, car elle n'y ajoute rien.
Voici votre requête réécrite. Nous voulons tous les ressorts pour smart ressort # 1 puis tous les articles pour ces ressorts. Parmi ceux-ci, nous montrons le plus récent 25.
CREATE INDEX idx1 ON smartressort_to_ressort (smartressort_id, ressort_id); CREATE INDEX idx2 ON article_to_ressort (ressort_id, article_id); CREATE INDEX idx3 ON article (id, created_at);
Maintenant, quels index seraient nécessaires pour aider le SGBD avec cela? Commencez par la table interne ( smartressort_to_ressort
). Nous accédons à tous les enregistrements avec un smartressort_id
donné et nous voulons obtenir le ressort_id
associé. L'index doit donc contenir ces deux colonnes dans cet ordre. Idem pour article_to_ressort
et son ressort_id
et article_id
. Enfin, nous voulons sélectionner les articles en fonction des identifiants des articles trouvés et les classer par created_at
.
SELECT * FROM article WHERE id IN ( SELECT article_id FROM article_to_ressort WHERE ressort_id IN ( SELECT ressort_id FROM smartressort_to_ressort WHERE smartressort_id = 1 ) ) ORDER BY created_at DESC LIMIT 25;
Quoi qu'il en soit, ces index ne sont qu'une offre à le SGBD. Il peut se prononcer contre eux. Cela est particulièrement vrai pour l'index de la table article
. À combien de lignes le SGBD s'attend-il à accéder pour un smartressort_id
, c'est-à-dire combien de lignes peuvent figurer dans la clause IN
? Si le SGBD pense que cela pourrait bien représenter environ 10% de tous les ID d'article, il peut déjà décider de plutôt lire le tableau séquentiellement plutôt que de se frayer un chemin dans l'index pour autant de lignes.
C'est beaucoup plus rapide (~ 35 ms). la requête explicative montre que seul le idx3
est utilisé. lignes montre 1262 x 1 x 1 x 1262.
Donc pour moi, la solution était la suivante:
1 PRIMARY s const PRIMARY PRIMARY 4 const 1 Using index 1 PRIMARY a index NULL source_created 7 NULL 25 1 PRIMARY str ref PRIMARY,ressort_id,idx1 PRIMARY 4 const 1 Using index 1 PRIMARY atr eq_ref PRIMARY,article_id,idx2 PRIMARY 8 com.nps.lvz-prod.str.ressort_id,com.nps.lvz-prod.a.id 1 Using index; FirstMatch(a)
Cela ne nécessite que ~ 35 ms. Explain ressemble à ceci:
explain SELECT a.*, NOW() FROM article as a USE INDEX (source_created) where a.id in (SELECT atr.article_id FROM smartressort AS s JOIN smartressort_to_ressort AS str ON s.id = str.smartressort_id JOIN article_to_ressort AS atr ON str.ressort_id = atr.ressort_id WHERE s.id = 1 ) ORDER BY a.created_at DESC LIMIT 25;
Malgré tout, cette requête Explain me convient mieux, mais je ne sais pas pourquoi exactement:
1 PRIMARY a index NULL source_created 7 NULL 1 1 PRIMARY <subquery2> eq_ref distinct_key distinct_key 4 func 1 2 MATERIALIZED str ref PRIMARY,ressort_id,idx1 PRIMARY 4 const 1 Using index 2 MATERIALIZED atr ref PRIMARY,article_id,idx2 PRIMARY 4 com.nps.lvz-prod.str.ressort_id 1262 Using index
Puisque cela indique MATERIALIZED
, vous pouvez tester avec une version différente.
Vous avez omis peut-être la partie la plus critique, qui est la définition de l'index
source_created
. Veuillez inclure cela.@Cid Je ne pense pas que cela ferait généralement une grande différence.
Forcer l'index
source_created
est inutile, vous n'utilisez pas la colonne indexée dans la jointure, seulement après.@Cid La clause
WHERE
est appliquée avant la jointure se produit, pas après :-)@TimBiegeleisen haha alors je me suis trompé, merci de l'avoir signalé, ce soir, je vais dormir un peu moins bête :)
"L'indication FORCE INDEX agit comme USE INDEX (index_list), avec l'ajout qu'une analyse de table est supposée être très coûteuse. En d'autres termes, une analyse de table n'est utilisée que s'il n'y a aucun moyen d'utiliser l'un des index nommés pour trouver des lignes dans le tableau. " À mon avis, dans la requête, mais ne peut pas utiliser uniquement l'index nommé (source_created), il effectue une analyse complète. Pouvez-vous essayer avec la clé primaire et nous montrer le résultat?
@TimBiegeleisen le
source_created est ici: KEY source_created (created_at)
donc c'est juste un index pour created_at@DanielE. forcer la clé primaire car l'index est également très lent. Le problème principal est la commande. La requête sans la commande par ne prend que ~ 35 ms.
Avez-vous résolu le problème? quelle était la solution?
En plus des réponses données , suivez les conseils sur de nombreux: nombreux tableaux ici .