Ich bin nur neugierig, warum eine aggregierte Abfrage mit einer GROUP BY
- Klausel so viel schneller ausgeführt wird als ohne eine.
Die Ausführung dieser Abfrage dauert beispielsweise fast 10 Sekunden
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
Während dieser weniger als eine Sekunde dauert
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate
In diesem Fall gibt es nur ein CreatedDate
, daher gibt die gruppierte Abfrage dieselben Ergebnisse zurück wie die nicht gruppierte.
Ich habe festgestellt, dass die Ausführungspläne für die beiden Abfragen unterschiedlich sind. Die zweite Abfrage verwendet Parallelität, die erste nicht.
Ist es normal, dass SQL Server eine aggregierte Abfrage anders auswertet, wenn sie keine GROUP BY-Klausel enthält? Und kann ich etwas tun, um die Leistung der ersten Abfrage zu verbessern, ohne eine GROUP BY
- Klausel zu verwenden?
Bearbeiten
Ich habe gerade gelernt, dass ich OPTION(querytraceon 8649)
verwenden kann, um den Kostenaufwand für Parallelität auf 0 zu setzen, wodurch die Abfrage eine gewisse Parallelität verwendet und die Laufzeit auf 2 Sekunden reduziert wird, obwohl ich nicht weiß, ob es Nachteile gibt Verwenden dieses Abfragehinweises.
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)
Ich würde immer noch eine kürzere Laufzeit bevorzugen, da die Abfrage einen Wert bei der Benutzerauswahl füllen soll und daher im Idealfall sofort wie die gruppierte Abfrage erfolgen sollte. Im Moment verpacke ich nur meine Anfrage, aber ich weiß, dass dies keine ideale Lösung ist.
SELECT Min(CreatedDate)
FROM
(
SELECT Min(CreatedDate) as CreatedDate
FROM MyTable WITH (NOLOCK)
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate
) as T
Bearbeiten Sie # 2
Als Antwort auf Martins Bitte um weitere Informationen :
Sowohl CreatedDate
als auch SomeIndexedValue
haben einen separaten nicht eindeutigen, nicht gruppierten Index. SomeIndexedValue
ist eigentlich ein varchar (7) -Feld, obwohl es einen numerischen Wert speichert, der auf die PK (int) einer anderen Tabelle zeigt. Die Beziehung zwischen den beiden Tabellen ist in der Datenbank nicht definiert. Ich soll die Datenbank überhaupt nicht ändern und kann nur Abfragen schreiben, die Daten abfragen.
MyTable
enthält über 3 Millionen Datensätze, und jedem Datensatz ist eine Gruppe zugeordnet, zu der er gehört (SomeIndexedValue
). Die Gruppen können zwischen 1 und 200.000 Datensätze umfassen
Es sieht so aus, als würde es wahrscheinlich einem Index für CreatedDate
folgen, um vom niedrigsten zum höchsten zu gelangen, und nachschlagen, um das Prädikat SomeIndexedValue = 1
Zu bewerten.
Wenn die erste übereinstimmende Zeile gefunden wird, wird sie ausgeführt, es werden jedoch möglicherweise viel mehr Suchvorgänge durchgeführt, als erwartet, bevor eine solche Zeile gefunden wird (es wird davon ausgegangen, dass die mit dem Prädikat übereinstimmenden Zeilen nach Datum zufällig verteilt sind).
Siehe meine Antwort hier für ein ähnliches Problem
Der ideale Index für diese Abfrage wäre einer für SomeIndexedValue, CreatedDate
. Angenommen, Sie können das nicht hinzufügen oder zumindest Ihren vorhandenen Index für SomeIndexedValue
cover CreatedDate
als eingeschlossene Spalte erstellen, können Sie versuchen, die Abfrage wie folgt neu zu schreiben
SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1
um zu verhindern, dass es diesen bestimmten Plan verwendet.
Können wir für MAXDOP steuern und eine bekannte Tabelle auswählen, z. B. AdventureWorks.Production.TransactionHistory?
Wenn ich dein Setup mit wiederhole
--#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
die Kosten sind identisch.
Abgesehen davon würde ich eine Indexsuche für Ihren indizierten Wert erwarten (um dies zu ermöglichen); Andernfalls werden wahrscheinlich Hash-Übereinstimmungen anstelle von Stream-Aggregaten angezeigt. Sie können die Leistung mit nicht gruppierten Indizes verbessern, die die Werte enthalten, die Sie aggregieren, oder eine indizierte Ansicht erstellen, die Ihre Aggregate als Spalten definiert. Dann würden Sie einen Clustered-Index, der Ihre Aggregationen enthält, durch eine indizierte ID treffen. In SQL Standard können Sie einfach die Ansicht erstellen und den WITH-Hinweis (NOEXPAND) verwenden.
Ein Beispiel (ich verwende MIN nicht, da es in indizierten Ansichten nicht funktioniert):
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
Meiner Meinung nach liegt der Grund für das Problem darin, dass der SQL Server-Optimierer nicht nach dem BEST-Plan sucht, sondern nach einem guten Plan. Dies geht aus der Tatsache hervor, dass die Abfrage nach dem Erzwingen der Parallelität viel schneller ausgeführt wurde, was der Optimierer hatte nicht alleine gemacht.
Ich habe auch viele Situationen gesehen, in denen das Umschreiben der Abfrage in einem anderen Format den Unterschied zwischen dem Parallelisieren ausmachte (obwohl die meisten Artikel in SQL die Parametrisierung empfehlen, habe ich festgestellt, dass es manchmal zu keiner Parallelisierung kommt, selbst wenn die Parameter, die abgehört wurden, mit denen von non identisch waren - Eine parallelisierte oder das Kombinieren von zwei Abfragen mit UNION ALL kann manchmal die Parallelisierung beseitigen.
Daher kann die richtige Lösung darin bestehen, verschiedene Arten des Schreibens der Abfrage auszuprobieren, z. B. temporäre Tabellen, Tabellenvariablen, cte, abgeleitete Tabellen, Parametrisierung usw., und auch mit den Indizes, indizierten Ansichten oder gefilterten Indizes zu spielen um den besten Plan zu bekommen.