Perché una query aggregata è significativamente più veloce con una clausola GROUP BY che senza una?


12

Sono solo curioso di sapere perché una query aggregata viene eseguita molto più velocemente con una GROUP BYclausola che senza una.

Ad esempio, l'esecuzione di questa query richiede quasi 10 secondi

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

Mentre questo richiede meno di un secondo

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

Ne esiste solo uno CreatedDatein questo caso, quindi la query raggruppata restituisce gli stessi risultati di quella non raggruppata.

Ho notato che i piani di esecuzione per le due query sono diversi: la seconda query utilizza il parallelismo mentre la prima query no.

Query1 Piano di esecuzione Query2 Piano di esecuzione

È normale che SQL Server valuti diversamente una query aggregata se non ha una clausola GROUP BY? E c'è qualcosa che posso fare per migliorare le prestazioni della prima query senza usare una GROUP BYclausola?

modificare

Ho appena imparato che posso usare OPTION(querytraceon 8649)per impostare il costo in termini di costi del parallelismo su 0, il che fa sì che la query utilizzi un po 'di parallelismo e riduce il tempo di esecuzione a 2 secondi, anche se non so se ci siano degli svantaggi nell'utilizzo di questo suggerimento per la query.

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

inserisci qui la descrizione dell'immagine

Preferirei comunque un tempo di esecuzione più breve poiché la query ha lo scopo di popolare un valore in base alla selezione dell'utente, quindi idealmente dovrebbe essere istantanea come la query raggruppata. In questo momento sto solo avvolgendo la mia domanda, ma so che non è davvero una soluzione ideale.

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

Modifica n. 2

In risposta alla richiesta di Martin per ulteriori informazioni :

Entrambi CreatedDatee SomeIndexedValuehanno un indice separato non univoco, non cluster su di essi. SomeIndexedValueè in realtà un campo varchar (7), anche se memorizza un valore numerico che punta al PK (int) di un'altra tabella. La relazione tra le due tabelle non è definita nel database. Non dovrei affatto cambiare il database e posso solo scrivere query che interrogano i dati.

MyTablecontiene oltre 3 milioni di record e ad ogni record viene assegnato un gruppo a cui appartiene ( SomeIndexedValue). I gruppi possono essere ovunque da 1 a 200.000 record

Risposte:


8

Sembra che stia probabilmente seguendo un indice CreatedDatein ordine dal più basso al più alto e sta effettuando ricerche per valutare il SomeIndexedValue = 1predicato.

Quando trova la prima riga corrispondente, viene eseguita, ma è probabile che esegua molte più ricerche di quante si aspetti prima di trovare tale riga (presuppone che le righe corrispondenti al predicato siano distribuite casualmente in base alla data).

Vedi la mia risposta qui per un problema simile

L'indice ideale per questa query sarebbe uno SomeIndexedValue, CreatedDate. Supponendo che non sia possibile aggiungere questo o almeno rendere l'indice esistente sulla SomeIndexedValuecopertina CreatedDatecome colonna inclusa, è possibile provare a riscrivere la query come segue

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

per impedirgli di utilizzare quel particolare piano.


2

Possiamo controllare per MAXDOP e scegliere una tabella nota, ad esempio AdventureWorks.Production.TransactionHistory?

Quando ripeto l'installazione usando

--#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 

i costi sono identici.

A parte ciò, mi aspetterei (realizzarlo) che un indice cerchi il tuo valore indicizzato; in caso contrario, probabilmente vedrai corrispondenze hash anziché aggregati di stream. È possibile migliorare le prestazioni con indici non cluster che includono i valori che si stanno aggregando e oppure creare una vista indicizzata che definisce gli aggregati come colonne. Quindi verrai colpito un indice cluster, che contiene le tue aggregazioni, da un ID indicizzato. In SQL Standard, puoi semplicemente creare la vista e usare il suggerimento WITH (NOEXPAND).

Un esempio (non utilizzo MIN, poiché non funziona nelle viste indicizzate):

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

MAXDOPimposta il massimo grado di parallelismo, che limita il numero di processori che la query può utilizzare. Questo in sostanza renderebbe la seconda query lenta quanto la prima, poiché rimuove le sue capacità di usare il parallelismo, che non è quello che voglio.
Rachel,

@ Rachel sono d'accordo; ma non possiamo confrontare nulla se non fissiamo alcune regole di base. Non riesco a confrontare facilmente un processo parallelo in esecuzione su 64 core con un singolo thread in esecuzione su uno. Alla fine, spero che tutte le nostre macchine abbiano almeno una CPU logica = -)
owire

0

A mio avviso, la ragione del problema è che l'ottimizzatore del server sql non sta cercando il piano MIGLIORE, ma cerca un buon piano, come è evidente dal fatto che dopo aver forzato il parallelismo la query è stata eseguita molto più velocemente, qualcosa che l'ottimizzatore aveva non fatto da solo.

Ho anche visto molte situazioni in cui riscrivere la query in un formato diverso era la differenza tra parallelizzare (ad esempio, sebbene la maggior parte degli articoli su SQL raccomandasse la parametrizzazione, l'ho trovato per causare a volte rumorosi parallelismi anche quando i parametri sniffati erano gli stessi di un non - parallelizzato o combinando due query con UNION ALL a volte è possibile eliminare la parallelizzazione).

Pertanto, la soluzione corretta potrebbe essere provando diversi modi di scrivere la query, come provare tabelle temporanee, variabili di tabella, cte, tabelle derivate, parametrizzazione e così via, e anche giocare con gli indici, le viste indicizzate o gli indici filtrati in per ottenere il miglior piano.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.