Perchè è una query di aggregazione significativamente più veloce con una clausola GROUP BY che senza uno?

dba.stackexchange https://dba.stackexchange.com/questions/15295

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

Piano di esecuzione Query1 Piano di esecuzione Query2

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)

entrare descrizione dell'immagine qui

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

È stato utile?

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top