2011-09-20 26 views
14

Stiamo riscontrando un'enorme differenza tra queste query.SQL perché SELECT COUNT (*), MIN (col), MAX (col) più veloce di SELECT MIN (col), MAX (col)

La query lente

SELECT MIN(col) AS Firstdate, MAX(col) AS Lastdate 
FROM table WHERE status = 'OK' AND fk = 4193 

Tabella 'tabella'. Numero di scansioni 2, letture logiche 2458969, letture fisiche 0, letture read-ahead 0, letture logiche lob 0, letture fisiche lob 0, letture lob read-ahead 0.

Tempi di esecuzione SQL Server: Tempo CPU = 1966 ms , tempo trascorso = 1955 ms.

La query veloce

SELECT count(*), MIN(col) AS Firstdate, MAX(col) AS Lastdate 
FROM table WHERE status = 'OK' AND fk = 4193 

Tabella 'tabella'. Conteggio scansioni 1, letture logiche 5, letture logiche 0 lob, letture fisiche lob 0, letture lob read-ahead 0.

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

Domanda

Qual è la ragione tra l'enorme differenza di prestazioni tra le query?

Aggiornamento Un piccolo aggiornamento sulla base di questioni indicate come commenti:

L'ordine di esecuzione o di esecuzione ripetuta cambia nulla prestazioni saggio. Non ci sono parametri aggiuntivi usati e il database (test) non sta facendo altro durante l'esecuzione.

query lente

|--Nested Loops(Inner Join) 
|--Stream Aggregate(DEFINE:([Expr1003]=MIN([DBTest].[dbo].[table].[startdate]))) 
    | |--Top(TOP EXPRESSION:((1))) 
    |   |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1008]) WITH ORDERED PREFETCH) 
    |    |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED FORWARD) 
    |    |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD) 
    |--Stream Aggregate(DEFINE:([Expr1004]=MAX([DBTest].[dbo].[table].[startdate]))) 
     |--Top(TOP EXPRESSION:((1))) 
      |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1009]) WITH ORDERED PREFETCH) 
        |--Index Scan(OBJECT:([DBTest].[dbo].[table].[startdate]), ORDERED BACKWARD) 
        |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[FK]=(5806) AND [DBTest].[dbo].[table].[status]<>'A') LOOKUP ORDERED FORWARD) 

veloce interrogazione

|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1012],0))) 
    |--Stream Aggregate(DEFINE:([Expr1012]=Count(*), [Expr1004]=MIN([DBTest].[dbo].[table].[startdate]), [Expr1005]=MAX([DBTest].[dbo].[table].[startdate]))) 
     |--Nested Loops(Inner Join, OUTER REFERENCES:([DBTest].[dbo].[table].[id], [Expr1011]) WITH UNORDERED PREFETCH) 
      |--Index Seek(OBJECT:([DBTest].[dbo].[table].[FK]), SEEK:([DBTest].[dbo].[table].[FK]=(5806)) ORDERED FORWARD) 
      |--Clustered Index Seek(OBJECT:([DBTest].[dbo].[table].[PK_table]), SEEK:([DBTest].[dbo].[table].[id]=[DBTest].[dbo].[table].[id]), WHERE:([DBTest].[dbo].[table].[status]<'A' OR [DBTest].[dbo].[table].[status]>'A') LOOKUP ORDERED FORWARD) 

The execution plan from SSMS

risposta

La risposta data dal di sotto Martin Smith sembra spiegare il problema. La versione super short è che l'analizzatore di query MS-SQL utilizza erroneamente un piano di query nella query lenta che causa una scansione completa della tabella.

Aggiunta di un conteggio (*), il suggerimento di query con (FORCESCAN) o un indice combinato alla data di inizio, FK e colonne di stato corregge il problema di prestazioni.

+2

cosa succede se si esegue nuovamente la prima query dopo la seconda query? – gbn

+1

Forse perché quando si utilizza un conteggio (*) non si controlla ogni record per fk = 4193? – nosbor

+1

Stai correndo uno dopo l'altro? In tal caso: cosa succede se metti 'DBCC DROPCLEANBUFFERS' e' DBCC FREEPROCCACHE' prima di entrambe le query? Cosa succede se cambi la sequenza: esegui prima la query veloce, poi quella lenta? –

risposta

24

La cardinalità Server SQL stimatore rende varie ipotesi di modellizzazione come

  • Indipendenza: distribuzioni di dati su diverse colonne sono indipendenti a meno informazioni di correlazione è disponibile.
  • Uniformità: all'interno di ciascuna fase dell'istogramma oggetto della statistica, i valori distinti vengono equamente distribuiti e ogni valore ha la stessa frequenza.

Source

Ci sono 810,064 righe della tabella.

avete la richiesta

SELECT COUNT(*), 
     MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM table 
WHERE status <> 'A' 
     AND fk = 4193 

1.893 (0,23%) filari incontrano il fk = 4193 predicato, e di questi due falliscono la parte status <> 'A' partita quindi nel complesso 1.891 e hanno bisogno di essere aggregati.

Hai anche due indici nessuno dei quali copre l'intera query.

Per la ricerca veloce Usa un indice su fk di trovare direttamente le righe in cui fk = 4193 poi deve fare 1.893 key lookups di trovare ogni riga l'indice cluster per verificare la status predicato e recuperare il startdate per l'aggregazione.

Quando si rimuove il COUNT(*) dalla SELECT lista di SQL Server non è più ha per elaborare ogni riga di qualificazione. Di conseguenza, considera un'altra opzione.

Hai un indice su startdate in modo che possa iniziare la scansione che fin dall'inizio, facendo ricerche chiave torna alla tabella di base e non appena trova la prima tappa riga corrispondente come ha trovato il MIN(startdate), Allo stesso modo il MAX può essere trovato con un'altra scansione che inizia l'altra estremità dell'indice e funziona all'indietro.

SQL Server stima che ognuna di queste scansioni finirà per elaborare 590 righe prima di raggiungere quella che corrisponde al predicato. Dando 1.180 occhiate totali contro 1.893, quindi sceglie questo piano.

Il valore 590 è solo table_size/estimated_number_of_rows_that_match. lo stimatore di cardinalità presuppone che le righe corrispondenti siano equamente distribuite in tutta la tabella.

Sfortunatamente le 1.891 righe che soddisfano il predicato sono non distribuite in modo casuale rispetto a startdate. In effetti sono tutti condensati in un singolo segmento di 8205 righe verso la fine dell'indice, il che significa che la scansione per arrivare allo MIN(startdate) finisce per fare 801.859 ricerche di chiavi prima che possa fermarsi.

Questo può essere riprodotto qui sotto.

CREATE TABLE T 
(
id int identity(1,1) primary key, 
startdate datetime, 
fk int, 
[status] char(1), 
Filler char(2000) 
) 

CREATE NONCLUSTERED INDEX ix ON T(startdate) 

INSERT INTO T 
SELECT TOP 810064 Getdate() - 1, 
        4192, 
        'B', 
        '' 
FROM sys.all_columns c1, 
     sys.all_columns c2 


UPDATE T 
SET fk = 4193, startdate = GETDATE() 
WHERE id BETWEEN 801859 and 803748 or id = 810064 

UPDATE T 
SET startdate = GETDATE() + 1 
WHERE id > 810064 


/*Both queries give the same plan. 
UPDATE STATISTICS T WITH FULLSCAN 
makes no difference*/ 

SELECT MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM T 
WHERE status <> 'A' AND fk = 4192 


SELECT MIN(startdate) AS Firstdate, 
     MAX(startdate) AS Lastdate 
FROM T 
WHERE status <> 'A' AND fk = 4193 

Si potrebbe considerare l'utilizzo di query suggerimenti per forzare il piano per utilizzare l'indice su fk piuttosto che startdate o aggiungere l'indice mancante suggerito evidenziato nel piano di esecuzione su (fk,status) INCLUDE (startdate) per evitare questo problema.

+0

L'eliminazione della colonna di stato dalla query rallenta entrambe le query di un paio di volte. – CodingBarfield

+0

Crea statistiche automatiche/Statistiche aggiornamento automatico è True – CodingBarfield

+0

Piano di esecuzione XML http://pastebin.com/mBcgHYkN Stiamo verificando ora se poteva essere un piano di manutenzione non in esecuzione. – CodingBarfield

Problemi correlati