it-swarm.com.de

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

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.

Query1 Execution PlanQuery2 Execution Plan

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)

enter image description here

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

12
Rachel

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.

8
Martin Smith

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
2
ooutwire

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.

0
yoel halb