Warum ist eine aggregierte Abfrage mit einer GROUP BY-Klausel wesentlich schneller als ohne?

12

Ich bin nur neugierig, warum eine aggregierte Abfrage mit einer GROUP BYKlausel so viel schneller ausgeführt wird als ohne.

Die Ausführung dieser Abfrage dauert beispielsweise fast 10 Sekunden

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

Während dieser dauert weniger als eine Sekunde

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

CreatedDateIn diesem Fall gibt es nur eine , sodass die gruppierte Abfrage dieselben Ergebnisse wie die nicht gruppierte zurückgibt.

Ich habe festgestellt, dass die Ausführungspläne für die beiden Abfragen unterschiedlich sind. Bei der zweiten Abfrage wird Parallelität verwendet, bei der ersten Abfrage nicht.

Ausführungsplan für Abfrage1 Ausführungsplan für Query2

Ist es normal, dass SQL Server eine aggregierte Abfrage anders auswertet, wenn keine GROUP BY-Klausel vorhanden ist? Und kann ich die Leistung der ersten Abfrage verbessern, ohne eine GROUP BYKlausel zu verwenden?

Bearbeiten

Ich habe gerade gelernt, dass ich OPTION(querytraceon 8649)den Kostenaufwand für Parallelität auf 0 setzen kann, wodurch die Abfrage eine gewisse Parallelität verwendet und die Laufzeit auf 2 Sekunden verkürzt wird, obwohl ich nicht weiß, ob die Verwendung dieses Abfragehinweises Nachteile hat.

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

Bildbeschreibung hier eingeben

Ich würde immer noch eine kürzere Laufzeit bevorzugen, da die Abfrage einen Wert bei der Benutzerauswahl auffüllen soll und daher im Idealfall wie die gruppierte Abfrage sofort ausgeführt werden sollte. Im Moment bringe ich nur meine Anfrage ein, aber ich weiß, dass das nicht wirklich eine 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 :

Beide CreatedDateund SomeIndexedValuehaben einen separaten nicht eindeutigen, nicht gruppierten Index. SomeIndexedValueist 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.

MyTableenthält über 3 Millionen Datensätze, und jedem Datensatz wird eine Gruppe zugewiesen, zu der er gehört ( SomeIndexedValue). Die Gruppen können zwischen 1 und 200.000 Datensätze umfassen

Rachel
quelle

Antworten:

8

Es sieht so aus, als würde es wahrscheinlich einem Index CreatedDatein der Reihenfolge vom niedrigsten zum höchsten folgen und Lookups durchführen, um das SomeIndexedValue = 1Prädikat zu bewerten .

Wenn die erste übereinstimmende Zeile gefunden wird, wird sie ausgeführt, aber es kann durchaus sein, dass wesentlich mehr Suchvorgänge ausgeführt werden, als erwartet, bevor eine solche Zeile gefunden wird (es wird davon ausgegangen, dass die dem Prädikat entsprechenden 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 SomeIndexedValue, CreatedDate . Angenommen, Sie können das nicht hinzufügen oder zumindest Ihren vorhandenen Index als eingeschlossene Spalte in SomeIndexedValueDeckblatt CreatedDateeinfügen, dann könnten Sie versuchen, die Abfrage wie folgt umzuschreiben

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

um zu verhindern, dass es diesen bestimmten Plan verwendet.

Martin Smith
quelle
2

Können wir 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 (veranlassen); 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 treffen, der Ihre Aggregationen enthält, und zwar anhand einer indizierten ID. In SQL Standard können Sie einfach die Ansicht erstellen und den WITH (NOEXPAND) -Hinweis 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
outwire
quelle
MAXDOPLegt den maximalen Grad der Parallelität fest, wodurch die Anzahl der Prozessoren begrenzt wird, die die Abfrage verwenden kann. Dies würde die zweite Abfrage im Grunde so langsam wie die erste ausführen, da dadurch die Parallelität nicht mehr verwendet werden kann. Dies ist nicht das, was ich möchte.
Rachel
@ Rachel Ich stimme zu; Aber wir können nichts vergleichen, es sei denn, wir legen einige Grundregeln fest. Ich kann einen parallelen Prozess, der auf 64 Kernen ausgeführt wird, nicht einfach mit einem einzelnen Thread vergleichen, der auf einem Kern ausgeführt wird. Am Ende hoffe ich, dass alle unsere Maschinen mindestens eine logische CPU haben = -)
ooutwire
0

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, was sich aus der Tatsache ergibt, 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 erlebt, in denen das Umschreiben der Abfrage in einem anderen Format den Unterschied zwischen dem Parallelisieren ausmachte (obwohl die meisten Artikel in SQL das Parametrisieren empfehlen, wurde festgestellt, dass es manchmal zu keiner Parallelisierung führt, auch wenn die Parameter nicht identisch sind - Durch Parallelisieren einer oder durch Kombinieren von zwei Abfragen mit UNION ALL kann die Parallelisierung manchmal beseitigt werden.

Als solche könnte die richtige Lösung darin bestehen, verschiedene Arten des Schreibens der Abfrage zu versuchen, z. B. temporäre Tabellen, Tabellenvariablen, cte, abgeleitete Tabellen, Parametrisierung usw., und auch mit den Indizes, indizierten Ansichten oder gefilterten Indizes in zu spielen Um den besten Plan zu bekommen.

yoel halb
quelle