Comparaison de deux requêtes dans SQL Server 2012

14

Je compare deux requêtes dans SQL Server 2012. Le but est d'utiliser toutes les informations pertinentes disponibles à partir de l'optimiseur de requêtes lors du choix de la meilleure requête. Les deux requêtes produisent les mêmes résultats; le orderid maximum pour tous les clients.

L'effacement du pool de mémoire tampon a été effectué avant d'exécuter chaque requête avec FREEPROCCACHE et DROPCLEANBUFFERS

En utilisant les informations fournies ci-dessous, quelle requête est le meilleur choix?

-- Query 1 - return the maximum order id for a customer
SELECT orderid, custid
FROM Sales.Orders AS O1
WHERE orderid = (SELECT MAX(O2.orderid)
                 FROM Sales.Orders AS O2
                 WHERE O2.custid = O1.custid);


-- Query 2 - return the maximum order id for a customer
SELECT MAX(orderid), custid
FROM Sales.Orders AS O1
group by custid
order by custid

TEMPS STATISTIQUE

Requête 1 TEMPS STATISTIQUE: Temps CPU = 0 ms, temps écoulé = 24 ms

Requête 2 TEMPS STATISTIQUE: Temps CPU = 0 ms, temps écoulé = 23 ms

STATISTIQUES IO

Requête 1 STATISTIQUES IO: Table 'Commandes'. Nombre de balayages 1, lectures logiques 5, lectures physiques 2, lectures anticipées 0, lectures logiques 0, lob lectures physiques 0, lob lectures anticipées 0.

Requête 2 STATISTIQUES IO: Table 'Commandes'. Nombre de balayages 1, lectures logiques 4, lectures physiques 1, lectures anticipées 8, lob lectures logiques 0, lob physiques lectures 0, lob lectures anticipées lisent 0.

Plans d'exécution

entrez la description de l'image ici

SELECT propriétés Query 1

entrez la description de l'image ici

SELECT propriétés Requête 2

entrez la description de l'image ici

Conclusions:

Requête 1

  1. Coût par lot de 48%
  2. Lectures logiques 5
  3. Lectures physiques 2
  4. Lectures anticipées: 0
  5. Temps CPU: 0 ms
  6. Temps écoulé 24 ms
  7. Coût estimé du sous-arbre: 0,0050276
  8. CompileCPU: 2
  9. CompileMemory: 384
  10. CompileTime: 2

Requête 2

  1. Coût par lot 52%
  2. Lectures logiques 4
  3. Physcial lit 1
  4. Lectures anticipées: 8
  5. Temps CPU 0
  6. Temps écoulé 23 ms
  7. Estimation du coût du sous-arbre: 0,0054782
  8. CompileCPU: 0
  9. CompileMemory: 192
  10. CompileTime: 0

Personnellement, même si Query 2 a un coût par lot plus élevé selon le plan graphique, je pense que c'est plus efficace que Query 1. Cela parce que la requête 2 nécessite moins de lectures logiques, a un temps écoulé légèrement inférieur, les valeurs compilecpu, compilememory et compiletime sont inférieur. les lectures avec lecture anticipée sont 8 pour la requête 2 et 0 pour la requête 1.

Mise à jour 12:03

Définition d'index clusterisé

ALTER TABLE [Sales].[Orders] ADD  CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED 
(
    [orderid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Index non clusterisé idx_nc_custid

CREATE NONCLUSTERED INDEX [idx_nc_custid] ON [Sales].[Orders]
(
    [custid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Craig Efrein
la source
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Paul White 9

Réponses:

10

J'adore votre approche de l'examen minutieux de l'optimisation des requêtes et de l'examen des options et des plans. Je souhaite que plus de développeurs fassent cela. Une mise en garde serait - toujours tester avec beaucoup de lignes, en regardant les lectures logiques, c'est un petit tableau. Essayez de générer un exemple de charge et réexécutez la requête. Un petit problème - dans votre requête principale, vous ne demandez pas de commande par, dans votre requête inférieure, vous êtes. Vous devez les comparer et les contraster avec la commande.

Je viens de créer rapidement une table SalesOrders avec 200 000 commandes client - toujours pas énorme par tout effort d'imagination. Et exécuté les requêtes avec ORDER BY dans chacune. J'ai aussi joué un peu avec les index.

Sans index cluster sur OrderID, juste un index non cluster sur CustID La deuxième requête a surperformé. Surtout avec la commande incluse dans chacun. Il y avait deux fois plus de lectures sur la première requête que sur la deuxième requête, et les pourcentages de coût étaient de 67% / 33% entre les requêtes.

Avec un index cluster sur OrderID et un index non cluster uniquement sur CustID Ils ont effectué une vitesse similaire et le même nombre exact de lectures.

Je vous suggère donc d'augmenter le nombre de lignes et de faire d'autres tests. Mais ma dernière analyse sur vos requêtes -

Vous pouvez les voir se comporter de façon plus similaire que vous ne le pensez lorsque vous augmentez les rangées, alors gardez cette mise en garde à l'esprit et testez de cette façon.

Si tout ce que vous voulez retourner est le OrderID maximum pour chaque client, et vous voulez déterminer que le OrderID étant le plus grand OrderID, la deuxième requête parmi ces deux est la meilleure façon de partir de mon état d'esprit - c'est un peu plus simple et bien que légèrement plus cher en fonction du coût des sous-arbres, il s'agit d'une instruction plus rapide et plus facile à déchiffrer. Si vous avez l'intention d'ajouter un jour d'autres colonnes à votre jeu de résultats? Ensuite, la première requête vous permet de le faire.

Mise à jour: Un de vos commentaires sous votre question était:

N'oubliez pas que trouver la meilleure requête dans cette question est un moyen d'affiner les techniques utilisées pour les comparer.

Mais le meilleur moyen de le faire - tester avec plus de données - garantit toujours que vous disposez de données cohérentes avec la production et la production future attendue. Les plans de requête commencent à rechercher des données lorsque vous donnez plus de lignes aux tables et essayez de conserver la distribution comme vous l'attendez en production. Et faites attention à des choses comme inclure Order By ou non, ici, je ne pense pas que cela fasse une énorme différence à la fin, mais ça vaut quand même la peine de creuser.

Votre approche de comparaison de ce niveau de détail et de données est bonne. Les coûts des sous-arborescences sont arbitraires et dénués de sens pour la plupart, mais méritent tout de même d'être examinés pour comparer les modifications / modifications ou même les requêtes. Il est très important d'examiner les statistiques temporelles et les entrées-sorties, tout comme le plan pour tout ce qui semble déplacé par rapport à la taille des données avec lesquelles vous travaillez et ce que vous essayez de faire.

Mike Walsh
la source
Bonjour à nouveau, merci pour vos remarques sur l'utilisation de volumes de données plus importants. Ce n'est pas la première fois que quelqu'un en parle. La dernière fois, c'était pour envisager une éventuelle fragmentation des fractionnements de page. Dans votre échantillon de 200 000 lignes, avez-vous vérifié la fragmentation?
Craig Efrein
Eh bien, dans mon petit exemple de ligne rapide de 200k, je ne me concentrais pas sur la fragmentation, non. Mais comme je l'ai fait, il n'y en aurait pas. J'ai créé une table, je l'ai remplie, puis j'ai créé les index. Ce sont donc des index fraîchement créés. Et cela ne changera pas l'approche de l'examen des plans de requête, ce qui semble être la question principale. Le volume de données est important - très important - pour regarder avec précision les plans de requête. J'ai souvent vu des cas où cela avait l'air bien en dev (avec 1-10 lignes) et était horrible en prod avec des données réelles. Mais votre approche est bonne et j'espère que cette info et la conversation dans les commentaires vous aideront
Mike Walsh
Puisque nous regroupons par custid, comment avez-vous rendu les valeurs custid assez aléatoires? Une chose dont je me souviens de mes lectures, c'est l'importance de valeurs distinctes. Si custid n'avait qu'un petit nombre de clients distincts, le coût de l'ensemble de flux serait irréaliste.
Craig Efrein
Je viens d'utiliser la fonction RAND pour créer 100 clients et en attribuer un au hasard à chaque ID de commande. Je faisais une vérification rapide. :)
Mike Walsh
Merci Mike pour toute votre aide. Une dernière question cependant. À partir des écrans de propriétés SELECT du plan d'exécution en 2012 que j'ai fournis dans ma question, à quelles valeurs faites-vous attention?
Craig Efrein