Question

Je suis juste curieux de savoir pourquoi une requête globale va beaucoup plus vite avec une clause de GROUP BY que sans.

Par exemple, cette requête prend près de 10 secondes pour exécuter

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

Alors que celui-ci prend moins d'une seconde

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate

Il n'y a qu'un seul CreatedDate dans ce cas, de sorte que la requête groupée renvoie les mêmes résultats que celui dissociées.

J'ai remarqué les plans d'exécution pour les deux requêtes sont différentes -. La deuxième requête utilise Parallélisme alors que la première requête ne pas

Query1 plan d'exécution Query2 plan d'exécution

Est-il normal pour le serveur SQL pour évaluer différemment une requête globale si elle ne dispose pas d'une clause GROUP BY? Et est-il quelque chose que je peux faire pour améliorer les performances de la 1ère requête sans utiliser une clause de GROUP BY?

Modifier

Je viens j'appris peux utiliser OPTION(querytraceon 8649) pour régler les frais généraux des coûts de parallélisme à 0, ce qui rend rend l'utilisation de requêtes un certain parallélisme et réduit la durée à 2 secondes, même si je ne sais pas s'il y a des inconvénients à l'utilisation de cette requête indice.

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)

entrer image description ici

Je préfère encore un moteur d'exécution plus courte puisque la requête est destinée à alimenter une valeur lors de la sélection de l'utilisateur, donc devrait idéalement être instantanée comme la requête groupée est. En ce moment, j'enroulant juste ma requête, mais je sais que ce n'est pas vraiment une solution idéale.

SELECT Min(CreatedDate)
FROM
(
    SELECT Min(CreatedDate) as CreatedDate
    FROM MyTable WITH (NOLOCK) 
    WHERE SomeIndexedValue = 1
    GROUP BY CreatedDate
) as T

Edit # 2

En réponse à la demande de Martin pour plus d'infos :

Les deux CreatedDate et SomeIndexedValue ont un indice non unique séparé, non-cluster sur eux. SomeIndexedValue est en fait un champ varchar (7), même si elle stocke une valeur numérique qui pointe vers le PK (int) d'une autre table. La relation entre les deux tables ne sont pas définies dans la base de données. Je ne suis pas censé changer la base de données du tout, et ne peut écrire des requêtes que les données de la requête.

MyTable contient plus de 3 millions d'enregistrements, et chaque enregistrement est associé à un groupe auquel il appartient (SomeIndexedValue). Les groupes peuvent varier de 1 à 200 000 enregistrements

Était-ce utile?

La solution

On dirait qu'il est probablement la suite d'un index sur CreatedDate afin de haut en bas et faire pour évaluer le lookups prédicat SomeIndexedValue = 1.

Quand il trouve la première ligne correspondante, il est fait, mais il pourrait bien être en train de faire beaucoup plus de recherches qu'il attend avant qu'il ne trouve une telle ligne (il assume les lignes correspondant à l'attribut sont distribués au hasard selon la date).

Voir ma réponse ici pour un problème similaire

L'indice idéal pour cette requête serait un sur SomeIndexedValue, CreatedDate. En supposant que vous ne pouvez pas ajouter que ou au moins faire votre index existant sur SomeIndexedValue de couverture CreatedDate comme une colonne inclus, vous pourriez essayer de réécrire la requête comme suit

SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1

pour l'empêcher d'utiliser ce plan particulier.

Autres conseils

Peut-on contrôler MAXDOP et choisissez une table connue, par exemple, AdventureWorks.Production.TransactionHistory?

Quand je répète votre configuration à l'aide

--#1
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

les coûts sont identiques.

En aparté, j'attendre (pour y arriver) un indice chercher sur votre valeur indexée; sinon, vous allez probablement voir les matchs de hachage au lieu des agrégats de flux. Vous pouvez améliorer les performances avec les index non-cluster qui incluent les valeurs que vous et l'agrégation ou de créer une vue indexée qui définit vos agrégats sous forme de colonnes. Ensuite, vous frapperez un index ordonné en clusters, qui contient vos agrégations, par un Indexed Id. En standard SQL, vous pouvez simplement créer la vue et utiliser le WITH (NOEXPAND).

Un exemple (je n'utilise pas MIN, car il ne fonctionne pas dans les vues indexées):

USE AdventureWorks ;
GO

-- Covering Index with Include
CREATE INDEX IX_CoverAndInclude
ON Production.TransactionHistory(TransactionDate) 
INCLUDE (Quantity) ;
GO

-- Indexed View
CREATE VIEW dbo.SumofQtyByTransDate
    WITH SCHEMABINDING
AS
SELECT 
      TransactionDate 
    , COUNT_BIG(*) AS NumberOfTransactions
    , SUM(Quantity) AS TotalTransactions
FROM Production.TransactionHistory
GROUP BY TransactionDate ;
GO

CREATE UNIQUE CLUSTERED INDEX SumofAllChargesIndex 
    ON dbo.SumofQtyByTransDate (TransactionDate) ;  
GO


--#1
SELECT SUM(Quantity) 
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(0))
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(IX_CoverAndInclude))
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

--#3
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO

A mon avis, la raison de ce problème est que l'optimiseur du serveur SQL ne cherche pas le meilleur plan plutôt il recherche un bon plan, comme il ressort du fait que, après avoir forcé le parallélisme de la requête exécutée beaucoup plus rapide, ce qui l'optimiseur avait pas fait sur son propre.

J'ai aussi vu de nombreuses situations où la réécriture de la requête dans un format différent a été la différence entre parallélisation (par exemple, bien que la plupart des articles sur SQL recommandent paramétrant je l'ai trouvé pour causer Noy paralléliser parfois même lorsque les paramètres reniflé étaient les mêmes comme un non parallélisée, ou la combinaison de deux requêtes avec UNION ALL peut parfois éliminer parallélisation).

En tant que tel la bonne solution est peut-être en essayant différentes façons d'écrire la requête, comme essayer des tables temporaires, les variables de table, cte, tables dérivées, paramétrage, etc., et aussi jouer avec les index, les vues indexées, ou index filtrés afin d'obtenir le meilleur plan.

Licencié sous: CC-BY-SA avec attribution
Non affilié à dba.stackexchange
scroll top