J'ai une requête dans laquelle l'utilisation select *
non seulement fait beaucoup moins de lectures, mais utilise également beaucoup moins de temps processeur que l'utilisation select c.Foo
.
Voici la requête:
select top 1000 c.ID
from ATable a
join BTable b on b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
join CTable c on c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
where (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff)
and b.IsVoided = 0
and c.ComplianceStatus in (3, 5)
and c.ShipmentStatus in (1, 5, 6)
order by a.LastAnalyzedDate
Cela s'est terminé avec 2 473 658 lectures logiques, principalement dans le tableau B. Il a utilisé 26 562 CPU et avait une durée de 7 965.
Voici le plan de requête généré:
Sur PasteThePlan: https://www.brentozar.com/pastetheplan/?id=BJAp2mQIQ
Lorsque je passe c.ID
à *
, la requête s'est terminée avec 107 049 lectures logiques, réparties assez uniformément entre les trois tables. Il utilisait 4 266 CPU et avait une durée de 1 147.
Voici le plan de requête généré:
Sur PasteThePlan: https://www.brentozar.com/pastetheplan/?id=SyZYn7QUQ
J'ai tenté d'utiliser les indices de requête suggérés par Joe Obbish, avec ces résultats:
select c.ID
sans indice: https://www.brentozar.com/pastetheplan/?id=SJfBdOELm
select c.ID
avec indice: https://www.brentozar.com/pastetheplan/ ? id = B1W ___ N87
select *
sans indice: https://www.brentozar.com/pastetheplan/?id=HJ6qddEIm
select *
avec indice: https://www.brentozar.com/pastetheplan/?id=rJhhudNIQ
L'utilisation de l' OPTION(LOOP JOIN)
indice avec select c.ID
a considérablement réduit le nombre de lectures par rapport à la version sans indice, mais il fait toujours environ 4x le nombre de lectures de la select *
requête sans aucun indice. L'ajout OPTION(RECOMPILE, HASH JOIN)
à la select *
requête l'a rendu bien pire que tout ce que j'ai essayé.
Après la mise à jour des statistiques sur les tables et leurs index à l'aide WITH FULLSCAN
, la select c.ID
requête s'exécute beaucoup plus rapidement:
select c.ID
avant la mise à jour: https://www.brentozar.com/pastetheplan/?id=SkiYoOEUm
select *
avant la mise à jour: https://www.brentozar.com/ pastetheplan /? id = ryrvodEUX
select c.ID
après la mise à jour: https://www.brentozar.com/pastetheplan/?id=B1MRoO487
select *
après la mise à jour: https://www.brentozar.com/pastetheplan/?id=Hk7si_V8m
select *
surpasse toujours select c.ID
en termes de durée totale et de lectures totales ( select *
a environ la moitié des lectures) mais il utilise plus de CPU. Dans l'ensemble, ils sont beaucoup plus proches qu'avant la mise à jour, mais les plans diffèrent toujours.
Le même comportement est observé sur 2016 en cours d'exécution en 2014 Mode de compatibilité et sur 2014. Qu'est-ce qui pourrait expliquer la disparité entre les deux plans? Se pourrait-il que les index "corrects" n'aient pas été créés? Des statistiques légèrement dépassées peuvent-elles en être la cause?
J'ai essayé de déplacer les prédicats vers la ON
partie de la jointure, de plusieurs manières, mais le plan de requête est le même à chaque fois.
Après la reconstruction d'index
J'ai reconstruit tous les index des trois tables impliquées dans la requête. c.ID
fait toujours le plus de lectures (plus de deux fois plus *
), mais l'utilisation du processeur représente environ la moitié de la *
version. La c.ID
aussi la version déversée dans tempdb sur le tri des ATable
:
c.ID
: https://www.brentozar.com/pastetheplan/?id=HyHIeDO87
*
: https://www.brentozar.com/pastetheplan/?id=rJ4deDOIQ
J'ai également essayé de le forcer à fonctionner sans parallélisme, ce qui m'a donné la requête la plus performante: https://www.brentozar.com/pastetheplan/?id=SJn9-vuLX
Je remarque le nombre d'exécutions d'opérateurs APRÈS la recherche d'index volumineux qui effectue l'ordre seulement 1000 fois dans la version à un seul thread, mais a fait beaucoup plus dans la version parallélisée, entre 2622 et 4315 exécutions de divers opérateurs.
select c.ID
requête beaucoup plus rapide, mais il fait toujours un travail supplémentaire que laselect *
requête, sans conseils, fait.Les statistiques obsolètes peuvent certainement amener l'optimiseur à choisir une mauvaise méthode de recherche des données. Avez-vous essayé de faire un
UPDATE STATISTICS ... WITH FULLSCAN
ou de faire un pleinREBUILD
sur l'index? Essayez cela et voyez si cela vous aide.MISE À JOUR
Selon une mise à jour du PO:
Donc, maintenant, si la seule action a été
UPDATE STATISTICS
, essayez de faire un indexREBUILD
(pasREORGANIZE
) comme je l'ai vu qui aide avec le nombre de lignes estimé où les deuxUPDATE STATISTICS
et l'indexREORGANIZE
ne l'ont pas fait.la source
a) réécrire chaque table en tant que sous-requête, en suivant ces règles:
b) SELECT - mettre les colonnes de jointure en premier
c) PREDICATES - aller dans leurs sous-requêtes respectives
d) ORDER BY - aller dans leur sous-requêtes respectives, trier sur JOIN COLUMNS FIRST
e) Ajoutez une requête wrapper pour votre tri final et SELECT.
L'idée est de pré-trier les colonnes de jointure à l'intérieur de chaque sous-sélection, en plaçant les colonnes de jointure en premier dans chaque liste de sélection.
Voici ce que je veux dire ...
la source
ORDER BY
n'est pas valide dans une sous-requête sans TOP, FORXML, etc. Je l'ai essayé sans lesORDER BY
clauses mais c'était le même plan.