Pourquoi ma requête MySQL avec un sous-sélecteur se bloque-t-elle?
Question
La requête suivante est bloquée: (bien que les sous-requêtes effectuées séparément conviennent)
Je ne sais pas comment faire en sorte que le tableau d'explication ait l'air correct. Si quelqu'un me le dit, je vais le nettoyer.
select
sum(grades.points)) as p,
from assignments
left join grades using (assignmentID)
where gradeID IN
(select grades.gradeID
from assignments
left join grades using (assignmentID)
where ... grades.date <= '1255503600' AND grades.date >= '984902400'
group by assignmentID order by grades.date DESC);
Je pense que le problème vient de la table des premières notes ... le type ALL avec autant de lignes semble en être la cause .. Tout est indexé.
J'ai téléchargé le tableau en tant qu'image. Impossible d'obtenir le bon formatage: http://imgur.com/AjX34.png
Un intervenant voulait la clause complète où:
explain extended select count(assignments.assignmentID) as asscount, sum(TRIM(TRAILING '-' FROM grades.points)) as p, sum(assignments.points) as t
from assignments left join grades using (assignmentID)
where gradeID IN
(select grades.gradeID from assignments left join grades using (assignmentID) left join as_types on as_types.ID = assignments.type
where assignments.classID = '7815'
and (assignments.type = 30170 )
and grades.contactID = 7141
and grades.points REGEXP '^[-]?[0-9]+[-]?'
and grades.points != '-'
and grades.points != ''
and (grades.pointsposs IS NULL or grades.pointsposs = '')
and grades.date <= '1255503600'
AND grades.date >= '984902400'
group by assignmentID
order by grades.date DESC);
La solution
Supposons que vous utilisiez une base de données Real (c'est-à-dire une base de données autre que MySQL, mais j'utiliserai Postgres comme exemple) pour effectuer cette requête:
SELECT * FROM ta WHERE aid IN (SELECT subquery)
une base de données réelle va examiner la sous-requête et estimer son nombre de lignes:
- Si le nombre de lignes est petit (disons, moins de quelques millions)
Il exécuterait la sous-requête, puis créerait un hachage d'identifiants en mémoire, ce qui les rendait également uniques, ce qui est une fonctionnalité de IN ().
Ensuite, si le nombre de lignes extraites de ta est une petite partie de ta, il utilisera un index approprié pour extraire les lignes. Ou, si une partie importante de la table est sélectionnée, elle la scannera entièrement et consultera chaque identifiant dans le hachage, ce qui est très rapide.
- Si toutefois le nombre de lignes de la sous-requête est assez grand
La base de données la réécrirait probablement en tant que jointure par fusion, en ajoutant un sort + unique à la sous-requête.
Cependant, vous utilisez MySQL. Dans ce cas, cela ne fera rien (il va ré-exécuter la sous-requête pour chaque ligne de votre table) donc cela prendra 1000 ans. Désolé.
Autres conseils
Voir "La lenteur insupportable de IN": http://www.artfulsoftware.com/infotree/queries.php#568
Super désordonné, mais: (merci pour l'aide de tout le monde)
SELECT *
FROM grades
LEFT JOIN assignments ON grades.assignmentID = assignments.assignmentID
RIGHT JOIN (
SELECT g.gradeID
FROM assignments a
LEFT JOIN grades g
USING ( assignmentID )
WHERE a.classID = '7815'
AND (
a.type =30170
)
AND g.contactID =7141
g.points
REGEXP '^[-]?[0-9]+[-]?'
AND g.points != '-'
AND g.points != ''
AND (
g.pointsposs IS NULL
OR g.pointsposs = ''
)
AND g.date <= '1255503600'
AND g.date >= '984902400'
GROUP BY assignmentID
ORDER BY g.date DESC
) AS t1 ON t1.gradeID = grades.gradeID
Si votre sous-requête fonctionne correctement lorsqu'elle est exécutée séparément, essayez d'utiliser un JOIN plutôt qu'un IN, comme ceci:
select count(assignments.assignmentID) as asscount, sum(TRIM(TRAILING '-' FROM grades.points)) as p, sum(assignments.points) as t
from assignments left join grades using (assignmentID)
join
(select grades.gradeID from assignments left join grades using (assignmentID) left join as_types on as_types.ID = assignments.type
where assignments.classID = '7815'
and (assignments.type = 30170 )
and grades.contactID = 7141
and grades.points REGEXP '^[-]?[0-9]+[-]?'
and grades.points != '-'
and grades.points != ''
and (grades.pointsposs IS NULL or grades.pointsposs = '')
and grades.date <= '1255503600'
AND grades.date >= '984902400'
group by assignmentID
order by grades.date DESC) using (gradeID);
Il n’ya vraiment pas assez d’informations pour répondre à votre question et vous avez placé une ... au milieu de la clause where, ce qui est étrange. Quelle est la taille des tables et quels sont les index?
Cela dit, s’il ya trop de termes dans une clause in, vous constaterez des performances sérieusement dégradées. Remplacez l'utilisation de in par une jointure droite .
Pour commencer, la table as_types de la clause in n'est pas utilisée. Rester connecté ne sert à rien, alors débarrassez-vous-en.
Cela laisse la clause in contenant uniquement la table des affectations et des notes de la requête externe. Il est clair que les assignations de modification wherees appartiennent à la clause where de la requête externe. Vous devez déplacer tous les éléments where grades = que ce soit dans la clause on de la jointure gauche vers les grades.
La requête est un peu difficile à suivre, mais je soupçonne que la sous-requête n’est pas du tout nécessaire. Il semble que votre requête soit fondamentalement la suivante:
SELECT FOO()
FROM assignments LEFT JOIN grades USING (assignmentID)
WHERE gradeID IN
(
SELECT grades.gradeID
FROM assignments LEFT JOIN grades USING (assignmentID)
WHERE your_conditions = TRUE
);
Mais vous ne faites rien de vraiment chic dans la clause where de la sous-requête. Je soupçonne quelque chose de plus semblable à
SELECT FOO()
FROM assignments LEFT JOIN grades USING (assignmentID)
GROUP BY groupings
WHERE your_conditions_with_some_tweaks = TRUE;
fonctionnerait aussi bien.
S'il me manque un peu de logique clé ici, veuillez commenter et je vais modifier / supprimer ce message.