Je suis simplement curieux de savoir pourquoi une requête agrégée s'exécute tellement plus rapidement avec une clause GROUP BY
Que sans une.
Par exemple, l'exécution de cette requête prend près de 10 secondes
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 CreatedDate
dans ce cas, donc la requête groupée renvoie les mêmes résultats que la requête non groupée.
J'ai remarqué que les plans d'exécution des deux requêtes sont différents - La deuxième requête utilise le parallélisme alors que la première ne le fait pas.
Est-il normal que SQL Server évalue une requête agrégée différemment s'il n'a pas de clause GROUP BY? Et puis-je faire quelque chose pour améliorer les performances de la première requête sans utiliser de clause GROUP BY
?
Modifier
Je viens d'apprendre que je peux utiliser OPTION(querytraceon 8649)
pour définir le coût supplémentaire du parallélisme à 0, ce qui oblige la requête à utiliser un certain parallélisme et réduit le temps d'exécution à 2 secondes, bien que je ne sache pas s'il y a des inconvénients à en utilisant cet indice de requête.
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)
Je préférerais toujours un temps d'exécution plus court car la requête est destinée à remplir une valeur lors de la sélection de l'utilisateur, donc devrait idéalement être instantanée comme la requête groupée. Pour le moment, je termine 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
Éditer # 2
En réponse à demande de Martin pour plus d'informations :
CreatedDate
et SomeIndexedValue
ont un index non unique et non cluster séparé. SomeIndexedValue
est en fait un champ varchar (7), même s'il stocke une valeur numérique qui pointe vers le PK (int) d'une autre table. La relation entre les deux tables n'est pas définie dans la base de données. Je ne suis pas du tout censé changer la base de données et je ne peux écrire que des requêtes qui interrogent des données.
MyTable
contient plus de 3 millions d'enregistrements, et à chaque enregistrement est affecté un groupe auquel il appartient (SomeIndexedValue
). Les groupes peuvent contenir de 1 à 200 000 enregistrements
Il semble qu'il suive probablement un index sur CreatedDate
dans l'ordre du plus bas au plus haut et effectue des recherches pour évaluer le SomeIndexedValue = 1
prédicat.
Lorsqu'il trouve la première ligne correspondante, cela est fait, mais il se peut bien qu'il effectue beaucoup plus de recherches qu'il ne l'attend avant de trouver une telle ligne (il suppose que les lignes correspondant au prédicat sont distribuées de manière aléatoire en fonction de la date.)
Voir ma réponse ici pour un problème similaire
L'index idéal pour cette requête serait un sur SomeIndexedValue, CreatedDate
. En supposant que vous ne pouvez pas ajouter cela ou au moins créer votre index existant sur SomeIndexedValue
couvrir CreatedDate
en tant que colonne incluse, vous pouvez 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.
Pouvons-nous contrôler MAXDOP et choisir une table connue, par exemple AdventureWorks.Production.TransactionHistory?
Lorsque je répète votre configuration en utilisant
--#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 passant, je m'attendrais (pour y arriver) à une recherche d'index sur votre valeur indexée; sinon, vous verrez probablement des correspondances de hachage au lieu d'agrégats de flux. Vous pouvez améliorer les performances avec des index non clusterisés qui incluent les valeurs que vous agrégez et ou créer une vue indexée qui définit vos agrégats sous forme de colonnes. Ensuite, vous atteindriez un index cluster, qui contient vos agrégations, par un ID indexé. Dans SQL Standard, vous pouvez simplement créer la vue et utiliser l'indicateur 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
À mon avis, la raison du problème est que l'optimiseur de serveur SQL ne recherche pas le meilleur plan, mais plutôt un bon plan, comme le montre le fait qu'après avoir forcé le parallélisme, la requête s'est exécutée beaucoup plus rapidement, ce que l'optimiseur avait pas fait tout seul.
J'ai également vu de nombreuses situations où la réécriture de la requête dans un format différent faisait la différence entre la parallélisation (par exemple, bien que la plupart des articles sur SQL recommandent le paramétrage, j'ai trouvé que cela entraînait parfois noy pour paralléliser même lorsque les paramètres reniflés étaient les mêmes qu'un non - une parallélisation, ou la combinaison de deux requêtes avec UNION ALL peut parfois éliminer la parallélisation).
En tant que telle, la bonne solution pourrait être d'essayer différentes manières d'écrire la requête, telles que les tables temporaires, les variables de table, le cte, les tables dérivées, le paramétrage, etc., et également jouer avec les index, les vues indexées ou les index filtrés dans afin d'obtenir le meilleur plan.