Perchè è una query di aggregazione significativamente più veloce con una clausola GROUP BY che senza uno?
-
16-10-2019 - |
Domanda
Sono solo curioso di sapere perchè una query di aggregazione viene eseguito in modo molto più veloce con una clausola GROUP BY
che senza uno.
Ad esempio, la query prende quasi 10 secondi per eseguire
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
Anche se questo richiede meno di un secondo
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate
C'è solo un CreatedDate
in questo caso, in modo che la query raggruppata restituisce gli stessi risultati come quello separati.
ho notato i piani di esecuzione per i due query sono diversi -. La seconda query utilizza parallelismo mentre la prima query non lo fa
E 'normale per il server SQL di valutare una query di aggregazione in modo diverso se non ha una clausola GROUP BY? E c'è qualcosa che posso fare per migliorare le prestazioni della prima interrogazione senza l'utilizzo di una clausola GROUP BY
?
Modifica
Ho appena appreso che posso usare OPTION(querytraceon 8649)
per impostare l'overhead costo di parallelismo a 0, il che rende rende l'utilizzo di query qualche parallelismo e riduce il tempo di esecuzione a 2 secondi, anche se non so se ci sono aspetti negativi a utilizzare questa query suggerimento.
SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)
mi piacerebbe ancora preferiscono un tempo di esecuzione più breve in quanto la query ha lo scopo di popolare un valore alla selezione da parte dell'utente, in modo da dovrebbero idealmente essere istantanea come la query raggruppata è. In questo momento mi sto solo confezionamento mia domanda, ma so che non è proprio la soluzione ideale.
SELECT Min(CreatedDate)
FROM
(
SELECT Min(CreatedDate) as CreatedDate
FROM MyTable WITH (NOLOCK)
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate
) as T
Modifica # 2
In risposta a la richiesta di Martin per ulteriori informazioni :
Sia CreatedDate
e SomeIndexedValue
hanno un non univoco, indice non cluster separata su di loro. SomeIndexedValue
è in realtà un (7) campo varchar, pur memorizza un valore numerico che punta al PK (int) di un'altra tabella. La relazione tra le due tabelle non è definita nel database. Io non dovrei modificare il database a tutti, e può solo le query di scrittura che i dati della query.
MyTable
contiene oltre 3 milioni di dischi, e ogni record viene assegnato un gruppo di appartenenza (SomeIndexedValue
). I gruppi possono essere ovunque da 1 a 200.000 record
Soluzione
Sembra che esso è probabilmente seguendo un indice su CreatedDate
in ordine dal più basso al più alto e facendo ricerche per valutare il predicato SomeIndexedValue = 1
.
Quando si trova la prima riga corrispondente è fatto, ma può anche essere facendo molte più ricerche di quanto lo aspetta prima che trovi tali righe un (assume le righe corrispondenti al predicato sono distribuite statisticamente in base alla data.)
See la mia risposta qui per un problema analogo
L'indice ideale per questa query sarebbe uno su SomeIndexedValue, CreatedDate
. Supponendo che non si può aggiungere che, o almeno rendere il vostro indice esistente sul coperchio SomeIndexedValue
CreatedDate
come colonna incluso allora si potrebbe provare a riscrivere la query come segue
SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1
per evitare che con quel particolare programma.
Altri suggerimenti
Possiamo controllare per MAXDOP e scegliere un tavolo noto, per esempio, AdventureWorks.Production.TransactionHistory?
Quando Ripeto la configurazione utilizzando
--#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.
Per inciso, mi sarei aspettato (realizzarlo) un indice di ricerca sul valore indicizzato; in caso contrario, è probabile che andando a vedere le partite di hash invece di aggregati di flusso. È possibile migliorare le prestazioni con indici non cluster che includono i valori che si sta aggregazione e o creare una vista indicizzata che definisce i aggregati come colonne. Poi si sarebbe colpire un indice cluster, che contiene le aggregazioni, da un indicizzato Id. In SQL standard, si può semplicemente creare la vista e utilizzare il suggerimento CON (NOEXPAND).
Un esempio (non uso MIN, dal momento che non funziona in 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
A mio parere il motivo per il problema è che l'ottimizzatore di SQL Server non è alla ricerca del miglior piano piuttosto sembra un buon piano, come è evidente dal fatto che dopo aver costretto il parallelismo query eseguita molto più veloce, cosa che l'ottimizzatore non aveva fatto su di essa la propria.
Ho anche visto molte situazioni in cui riscrivere la query in un formato diverso era la differenza tra parallelizzare (ad esempio, anche se la maggior parte degli articoli su SQL consiglia parametrizzazione ho trovato per causare talvolta noy per parallelizzare anche quando i parametri annusato erano uguali come uno non parallelizzato, o combinando due query con UNION ALL volte può eliminare parallelizzazione).
In quanto tale la soluzione giusta potrebbe essere provando diversi modi di scrivere query, come ad esempio cercando di tabelle temporanee, variabili di tabella, CTE, tabelle derivate, parametrizzazione, e così via, e anche giocare con gli indici, viste indicizzate, o indici filtrati in modo da ottenere il miglior piano.