GROUP BY con MAX rispetto a MAX


8

Sono un programmatore, ho a che fare con un grande tavolo con il seguente schema:

UpdateTime, PK, datetime, notnull
Name, PK, char(14), notnull
TheData, float

È attivo un indice cluster Name, UpdateTime

Mi chiedevo cosa dovrebbe essere più veloce:

SELECT MAX(UpdateTime)
FROM [MyTable]

o

SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM [MyTable]
    group by [UpdateTime]
   ) as t

Gli inserti in questa tabella sono in blocchi di 50.000 righe con la stessa data . Quindi ho pensato che il raggruppamento potrebbe facilitare il MAXcalcolo.

Invece di provare a trovare un massimo di 150.000 righe, il raggruppamento per 3 righe, e quindi il calcolo di MAXsarebbe più veloce? La mia ipotesi è corretta o anche il gruppo è costoso?

Risposte:


12

Ho creato la tabella big_table secondo il tuo schema

create table big_table
(
    updatetime datetime not null,
    name char(14) not null,
    TheData float,
    primary key(Name,updatetime)
)

Ho quindi riempito la tabella con 50.000 righe con questo codice:

DECLARE @ROWNUM as bigint = 1
WHILE(1=1)
BEGIN
    set @rownum  = @ROWNUM + 1
    insert into big_table values(getdate(),'name' + cast(@rownum as CHAR), cast(@rownum as float))
    if @ROWNUM > 50000
        BREAK;  
END

Utilizzando SSMS, ho quindi testato entrambe le query e mi sono reso conto che nella prima query stai cercando il MAX di TheData e nel secondo, il MAX di updatetime

Ho quindi modificato la prima query per ottenere anche il massimo di updatetime

set statistics time on -- execution time
set statistics io on -- io stats (how many pages read, temp tables)

-- query 1
SELECT MAX([UpdateTime])
FROM big_table

-- query 2
SELECT MAX([UpdateTime]) AS value
from
   (
    SELECT [UpdateTime]
    FROM big_table
    group by [UpdateTime]
   ) as t


set statistics time off
set statistics io off

Usando il tempo delle statistiche ottengo il numero di millisecondi necessari per analizzare, compilare ed eseguire ogni istruzione

Utilizzando Statistics IO ottengo informazioni sull'attività del disco

STATISTICS TIME e STATISTICS IO forniscono informazioni utili. Come ad esempio le tabelle temporanee utilizzate (indicate dal tavolo di lavoro). Inoltre, quante pagine logiche sono state lette, il che indica il numero di pagine del database lette dalla cache.

Quindi attivo il piano di esecuzione con CTRL + M (attiva mostra il piano di esecuzione effettivo) e quindi eseguo con F5.

Ciò fornirà un confronto di entrambe le query.

Ecco l'output della scheda Messaggi

- Query 1

Tabella 'big_table'. Conteggio scansioni 1, letture logiche 543 , letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 16 ms, tempo trascorso = 6 ms .

- Query 2

Tabella " Tavolo da lavoro ". Conteggio scansioni 0, letture logiche 0, letture fisiche 0, letture read-ahead 0, letture logiche lob 0, letture fisiche lob 0, letture read lob 0.

Tabella 'big_table'. Conteggio scansioni 1, letture logiche 543 , letture fisiche 0, letture read-ahead 0, letture log lob 0, letture fisiche lob 0, letture read lob 0.

Tempi di esecuzione di SQL Server: tempo CPU = 0 ms, tempo trascorso = 35 ms .

Entrambe le query generano 543 letture logiche, ma la seconda query ha un tempo trascorso di 35 ms, mentre la prima ha solo 6 ms. Noterai anche che la seconda query comporta l'uso di tabelle temporanee in tempdb, indicato dalla parola worktable . Anche se tutti i valori per worktable sono a 0, il lavoro è stato ancora svolto in tempdb.

Quindi c'è l'output dalla scheda del piano di esecuzione effettivo accanto alla scheda Messaggi

inserisci qui la descrizione dell'immagine

Secondo il piano di esecuzione fornito da MSSQL, la seconda query fornita ha un costo batch totale del 64%, mentre la prima costa solo il 36% del batch totale, quindi la prima query richiede meno lavoro.

Usando SSMS, puoi testare e confrontare le tue query e scoprire esattamente come MSSQL sta analizzando le tue query e quali oggetti: tabelle, indici e / o statistiche se ne vengono utilizzati per soddisfare tali query.

Una nota a margine aggiuntiva da tenere a mente durante il test è la pulizia della cache prima del test, se possibile. Questo aiuta a garantire che i confronti siano accurati e questo è importante quando si pensa all'attività del disco. Comincio con DBCC DROPCLEANBUFFERS e DBCC FREEPROCCACHE per cancellare tutta la cache. Fare attenzione, tuttavia, a non utilizzare questi comandi su un server di produzione effettivamente in uso, poiché si forzerà efficacemente il server a leggere tutto dal disco alla memoria.

Ecco la documentazione pertinente.

  1. Svuota la cache del piano con DBCC FREEPROCCACHE
  2. Cancella tutto dal pool di buffer con DBCC DROPCLEANBUFFERS

L'uso di questi comandi potrebbe non essere possibile a seconda dell'utilizzo dell'ambiente.

Aggiornato il 28/10 12:46

Correzioni all'immagine del piano di esecuzione e all'output delle statistiche.


Grazie per la risposta profonda, si prega di notare la mia linea calva nel codice, ogni gruppo di 50.000 righe ha la stessa data che è diversa da altri blocchi. Quindi dovrei getdate()uscire dal giro
Ofiris il

1
Ciao @Ofiris. La risposta che ho dato è in realtà solo per aiutarti a fare il confronto da solo. Ho creato dati spazzatura casuali solo per illustrare l'uso dei vari comandi e strumenti che è possibile utilizzare per giungere alle proprie conclusioni.
Craig Efrein,

1
Nessun lavoro è stato fatto in tempdb. La tabella di lavoro consiste nel gestire le partizioni nel caso in cui l'aggregato di hash debba passare a tempdb perché è stata riservata una memoria insufficiente. Si prega di sottolineare che i costi sono sempre stimati anche in un piano "reale". Sono le stime dell'ottimizzatore, che potrebbero non avere molta relazione con le prestazioni effettive. Non utilizzare% di batch come metrica di ottimizzazione primaria. La cancellazione dei buffer è importante solo se si desidera testare le prestazioni della cache fredda.
Paul White 9

1
Ciao @PaulWhite. Grazie per le informazioni aggiuntive, apprezzo sinceramente qualsiasi suggerimento su come essere più esatti. Tuttavia, quando pronunci le tue frasi: "Non usare", ciò non può essere interpretato erroneamente come dare un ordine piuttosto che offrire una consulenza professionale? I migliori saluti.
Craig Efrein,

@CraigEfrein Probabilmente. Ero breve per adattarmi allo spazio dei commenti consentito.
Paul White 9

6

Gli inserti in questa tabella sono in blocchi di 50.000 righe con la stessa data. Quindi ho pensato che il raggruppamento potrebbe facilitare il calcolo del MAX.

La riscrittura potrebbe aver aiutato se SQL Server ha implementato la scansione skip dell'indice, ma non è così.

La scansione skip dell'indice consente a un motore di database di cercare il successivo valore dell'indice diverso invece di scansionare tutti i duplicati (o sottochiavi irrilevanti) in mezzo. Nel tuo caso, skip-scan consentirebbe al motore di trovare MAX(UpdateTime)il primo Name, passare al MAX(UpdateTime)secondo Name... e così via. Il passo finale sarebbe quello di trovare i MAX(UpdateTime)candidati uno per nome.

Puoi simularlo in una certa misura usando un CTE ricorsivo, ma è un po 'disordinato e non efficiente quanto lo skip-scan integrato sarebbe:

WITH RecursiveCTE
AS
(
    -- Anchor: MAX UpdateTime for
    -- highest-sorting Name
    SELECT TOP (1)
        BT.Name,
        BT.UpdateTime
    FROM dbo.BigTable AS BT
    ORDER BY
        BT.Name DESC,
        BT.UpdateTime DESC

    UNION ALL

    -- Recursive part
    -- MAX UpdateTime for Name
    -- that sorts immediately lower
    SELECT
        SubQuery.Name,
        SubQuery.UpdateTime
    FROM 
    (
        SELECT
            BT.Name,
            BT.UpdateTime,
            rn = ROW_NUMBER() OVER (
                ORDER BY BT.Name DESC, BT.UpdateTime DESC)
        FROM RecursiveCTE AS R
        JOIN dbo.BigTable AS BT
            ON BT.Name < R.Name
    ) AS SubQuery
    WHERE
        SubQuery.rn = 1
)
-- Final MAX aggregate over
-- MAX(UpdateTime) per Name
SELECT MAX(UpdateTime) 
FROM RecursiveCTE
OPTION (MAXRECURSION 0);

Piano CTE ricorsivo

Quel piano esegue una ricerca singleton per ogni distinto Name, quindi trova il massimo UpdateTimedai candidati. Le prestazioni relative a una semplice scansione completa della tabella dipendono dal numero di duplicati disponibili Namee dal fatto che le pagine toccate dalle ricerche singleton siano o meno in memoria.

Soluzioni alternative

Se sei in grado di creare un nuovo indice su questa tabella, una buona scelta per questa query sarebbe un indice UpdateTimesolo:

CREATE INDEX IX__BigTable_UpdateTime 
ON dbo.BigTable (UpdateTime);

Questo indice consentirà al motore di esecuzione di trovare il massimo UpdateTimecon una ricerca singleton alla fine dell'albero b dell'indice:

Nuovo piano indicizzato

Questo piano consuma solo pochi IO logici (per navigare nei livelli b-tree) e si completa immediatamente. Si noti che la scansione dell'indice nel piano non è una scansione completa del nuovo indice, ma restituisce semplicemente una riga dall'unica "fine" dell'indice.

Se non si desidera creare un nuovo indice completo sulla tabella, è possibile prendere in considerazione una vista indicizzata contenente solo i UpdateTimevalori univoci :

CREATE VIEW dbo.BigTableUpdateTimes
WITH SCHEMABINDING AS
SELECT 
    UpdateTime, 
    NumRows = COUNT_BIG(*)
FROM dbo.BigTable AS BT
GROUP BY
    UpdateTime;
GO
CREATE UNIQUE CLUSTERED INDEX cuq
ON dbo.BigTableUpdateTimes (UpdateTime);

Ciò ha il vantaggio di creare solo una struttura con tante righe quanti sono i UpdateTimevalori univoci , anche se ogni query che modifica i dati nella tabella di base avrà operatori aggiuntivi aggiunti al suo piano di esecuzione per mantenere la vista indicizzata. La query per trovare il UpdateTimevalore massimo sarebbe:

SELECT MAX(BTUT.UpdateTime)
FROM dbo.BigTableUpdateTimes AS BTUT
    WITH (NOEXPAND);

Piano di visualizzazione indicizzato

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.