2009-10-20 9 views
73

ho usato per scrivere il mio EXISTS controlli in questo modo:sottoquery usando ci sono 1 o esiste *

IF EXISTS (SELECT * FROM TABLE WHERE [email protected]) 
BEGIN 
    UPDATE TABLE SET ColumnsX=ValuesX WHERE Where [email protected] 
END 

Uno dei DBA in una vita precedente mi ha detto che quando faccio una clausola EXISTS, utilizzare SELECT 1 invece di SELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE [email protected]) 
BEGIN 
    UPDATE TABLE SET ColumnsX=ValuesX WHERE [email protected] 
END 

Questo fa davvero la differenza?

+1

hai dimenticato EXISTS (SELECT NULL FROM ...). Questo è stato chiesto di recente tra –

+13

p.s. ottenere un nuovo DBA. La superstizione non ha spazio nell'IT, specialmente nella gestione dei database (da un ex DBA !!!) –

risposta

110

No. Questo è stato coperto un periodo di migliaia di miliardi. SQL Server è intelligente e sa che viene utilizzato per un EXISTS e restituisce NESSUN DATO al sistema.

Disse Microsoft: http://technet.microsoft.com/en-us/library/ms189259.aspx?ppud=4

L'elenco di selezione di una sottoquery introdotto da ESISTE quasi sempre costituito da un asterisco (*). C'è nessun motivo per elencare i nomi delle colonne perché si sta solo verificando se le righe che soddisfano le condizioni specificate nella sottoquery esistono le righe che sono .

Inoltre, non credermi? Provare a eseguire le seguenti operazioni:

SELECT whatever 
    FROM yourtable 
WHERE EXISTS(SELECT 1/0 
       FROM someothertable 
       WHERE a_valid_clause) 

Se è stato effettivamente facendo qualcosa con l'elenco SELECT, sarebbe lanciare un div per zero. Non è così.

EDIT: Nota, lo standard SQL in realtà parla di questo.

ANSI SQL 1992 standard, pg 191 http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

3) Caso:
a) Se il <select list> "*" è semplicemente contenuta in un <subquery> che è immediatamente contenuta in un <exists predicate>, poi il <select list> è equivalente a <value expression> che è un arbitrario <literal>.

+56

+1 per 1/0 nella clausola EXISTS. – gbn

+1

il trucco 'EXISTS' con 1/0 può essere esteso anche a questo' SELECT 1 WHERE EXISTS (SELECT 1/0) '... sembra un passo più astratto di quello che il secondo' SELECT' non ha clausola 'FROM' – whytheq

+1

@whytheq - Oppure 'SELECT COUNT (*) WHERE EXISTS (SELECT 1/0)'. Un 'SELECT' senza un' FROM' in SQL Server viene trattato come se stesse accedendo a una singola tabella di righe (ad es.simile alla selezione dalla tabella 'dual' in altri RDBMSs) –

7

Il modo migliore per sapere è eseguire il test delle prestazioni in entrambe le versioni e verificare il piano di esecuzione per entrambe le versioni. Scegli un tavolo con un sacco di colonne.

+2

+1. Non ho idea del perché questo è stato sottovalutato. Ho sempre pensato che fosse meglio insegnare a un uomo a pescare, piuttosto che dargli un pesce. In che modo le persone impareranno qualcosa? –

+1

+1 Fai da te consigli è sempre il benvenuto. –

-1

Nessuna differenza reale, ma potrebbe esserci un impatto molto piccolo sulle prestazioni. Come regola generale non dovresti chiedere più dati del necessario.

1

Personalmente trovo molto, molto difficile credere che non ottimizzino lo stesso piano di query. Ma l'unico modo per sapere nella tua situazione particolare è testarlo. Se lo fai, per favore segnalalo!

5

Non c'è alcuna differenza in SQL Server e non è mai stato un problema in SQL Server. L'ottimizzatore sa che sono gli stessi. Se guardi i piani di esecuzione, vedrai che sono identici.

89

La ragione di questo equivoco è presumibilmente dovuta alla convinzione che finirà per leggere tutte le colonne. È facile vedere che questo non è il caso.

CREATE TABLE T 
(
X INT PRIMARY KEY, 
Y INT, 
Z CHAR(8000) 
) 

CREATE NONCLUSTERED INDEX NarrowIndex ON T(Y) 

IF EXISTS (SELECT * FROM T) 
    PRINT 'Y' 

Dà piano

Plan

Questo dimostra che SQL Server è in grado di utilizzare l'indice stretto a disposizione per controllare il risultato, nonostante il fatto che l'indice non include tutte le colonne. L'accesso all'indice è sotto un operatore semi join che significa che può interrompere la scansione non appena viene restituita la prima riga.

Quindi è chiaro che la convinzione sopra è sbagliata.

Tuttavia Conor Cunningham dal team Query Optimiser spiega here che egli utilizza in genere SELECT 1 in questo caso, come si può fare una differenza di prestazioni minore nella compilazione della query.

La QP prenderà ed espandere l' presto in cantiere e tutti * li legano a oggetti (in questo caso, l'elenco dei colonne). Quindi rimuoverà le colonne non necessarie a causa della natura della query .

Così, per una semplice EXISTS sottoquery come questo:

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2) Il * sarà ampliato per alcuni potenzialmente grande elenco colonna e allora sarà stabilito che la semantica del EXISTS non richiede alcuna di quelle colonne , quindi fondamentalmente tutte possono essere rimosse .

"SELECT 1" eviterà di dover esaminare eventuali metadati non necessari per tale tabella durante la compilazione della query.

Tuttavia, in fase di esecuzione le due forme di query saranno identiche e avranno hanno runtime identici.

Ho testato quattro possibili modi di esprimere questa query su una tabella vuota con vari numeri di colonne. SELECT 1 vs SELECT * vs SELECT Primary_Key vs SELECT Other_Not_Null_Column.

Ho eseguito le query in un ciclo utilizzando OPTION (RECOMPILE) e misurato il numero medio di esecuzioni al secondo. Risultati qui sotto

enter image description here

+-------------+----------+---------+---------+--------------+ 
| Num of Cols | *  | 1 | PK | Not Null col | 
+-------------+----------+---------+---------+--------------+ 
| 2   | 2043.5 | 2043.25 | 2073.5 | 2067.5  | 
| 4   | 2038.75 | 2041.25 | 2067.5 | 2067.5  | 
| 8   | 2015.75 | 2017 | 2059.75 | 2059   | 
| 16   | 2005.75 | 2005.25 | 2025.25 | 2035.75  | 
| 32   | 1963.25 | 1967.25 | 2001.25 | 1992.75  | 
| 64   | 1903  | 1904 | 1936.25 | 1939.75  | 
| 128   | 1778.75 | 1779.75 | 1799 | 1806.75  | 
| 256   | 1530.75 | 1526.5 | 1542.75 | 1541.25  | 
| 512   | 1195  | 1189.75 | 1203.75 | 1198.5  | 
| 1024  | 694.75 | 697  | 699  | 699.25  | 
+-------------+----------+---------+---------+--------------+ 
| Total  | 17169.25 | 17171 | 17408 | 17408  | 
+-------------+----------+---------+---------+--------------+ 

Come si può vedere non c'è un vincitore coerente tra SELECT 1 e SELECT * e la differenza tra i due approcci è trascurabile. Lo SELECT Not Null col e lo SELECT PK appaiono leggermente più veloci.

Tutte e quattro le query peggiorano in termini di prestazioni all'aumentare del numero di colonne nella tabella.

Poiché la tabella è vuota, questa relazione sembra spiegabile solo in base alla quantità di metadati della colonna. Per COUNT(1) è facile vedere che questo viene riscritto su COUNT(*) a un certo punto nel processo dal seguente.

SET SHOWPLAN_TEXT ON; 

GO 

SELECT COUNT(1) 
FROM master..spt_values 

che dà il seguente piano

|--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1004],0))) 
     |--Stream Aggregate(DEFINE:([Expr1004]=Count(*))) 
      |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc])) 

Collegamento di un debugger al processo SQL Server e in modo casuale la rottura, mentre l'esecuzione del sottostante

DECLARE @V int 

WHILE (1=1) 
    SELECT @V=1 WHERE EXISTS (SELECT 1 FROM ##T) OPTION(RECOMPILE) 

ho scoperto che nei casi in cui la tabella ha 1024 colonne la maggior parte delle volte che lo stack di chiamate assomiglia a qualcosa di simile al seguente, indicando che effettivamente sta spendendo una grande proporzione del tempo che carica i metadati della colonna anche se n SELECT 1 viene utilizzato (per il caso in cui il tavolo ha 1 colonna in modo casuale rottura non ha colpito questo bit dello stack di chiamate in 10 tentativi)

sqlservr.exe!CMEDAccess::GetProxyBaseIntnl() - 0x1e2c79 bytes 
sqlservr.exe!CMEDProxyRelation::GetColumn() + 0x57 bytes 
sqlservr.exe!CAlgTableMetadata::LoadColumns() + 0x256 bytes  
sqlservr.exe!CAlgTableMetadata::Bind() + 0x15c bytes 
sqlservr.exe!CRelOp_Get::BindTree() + 0x98 bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CRelOp_FromList::BindTree() + 0x5c bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CRelOp_QuerySpec::BindTree() + 0xbe bytes 
sqlservr.exe!COptExpr::BindTree() + 0x58 bytes 
sqlservr.exe!CScaOp_Exists::BindScalarTree() + 0x72 bytes 
... Lines omitted ... 
msvcr80.dll!_threadstartex(void * ptd=0x0031d888) Line 326 + 0x5 bytes C 
[email protected]() + 0x37 bytes 

Questo tentativo profilazione manuale è sostenuta dal codice profiler VS 2012 che mostra una selezione molto diversa di funzioni che consumano il tempo di compilazione per i due casi (Top 15 Functions 1024 columns vs Top 15 Functions 1 column).

Entrambe le versioni SELECT 1 e SELECT * terminano con il controllo delle autorizzazioni della colonna e falliscono se all'utente non è concesso l'accesso a tutte le colonne della tabella.

Un esempio ho cribbed da una conversazione su the heap

CREATE USER blat WITHOUT LOGIN; 
GO 
CREATE TABLE dbo.T 
(
X INT PRIMARY KEY, 
Y INT, 
Z CHAR(8000) 
) 
GO 

GRANT SELECT ON dbo.T TO blat; 
DENY SELECT ON dbo.T(Z) TO blat; 
GO 
EXECUTE AS USER = 'blat'; 
GO 

SELECT 1 
WHERE EXISTS (SELECT 1 
       FROM T); 
/* ↑↑↑↑ 
Fails unexpectedly with 

The SELECT permission was denied on the column 'Z' of the 
      object 'T', database 'tempdb', schema 'dbo'.*/ 

GO 
REVERT; 
DROP USER blat 
DROP TABLE T 

Così si potrebbe ipotizzare che la differenza apparente minore quando si utilizza SELECT some_not_null_col è che si snoda solo fino controllando i permessi su quella specifica colonna (anche se carica ancora i metadati per tutti). Tuttavia questo non sembra adattarsi ai fatti come la differenza percentuale tra i due approcci, se qualcosa diventa più piccolo man mano che aumenta il numero di colonne nella tabella sottostante.

In ogni caso, non andrò rapidamente a sostituire tutte le mie query con questo modulo poiché la differenza è molto piccola e apparente solo durante la compilazione della query. La rimozione di OPTION (RECOMPILE) in modo che le esecuzioni successive possano utilizzare un piano memorizzato nella cache ha fornito quanto segue.

enter image description here

+-------------+-----------+------------+-----------+--------------+ 
| Num of Cols |  *  |  1  | PK  | Not Null col | 
+-------------+-----------+------------+-----------+--------------+ 
| 2   | 144933.25 | 145292  | 146029.25 | 143973.5  | 
| 4   | 146084 | 146633.5 | 146018.75 | 146581.25 | 
| 8   | 143145.25 | 144393.25 | 145723.5 | 144790.25 | 
| 16   | 145191.75 | 145174  | 144755.5 | 146666.75 | 
| 32   | 144624 | 145483.75 | 143531 | 145366.25 | 
| 64   | 145459.25 | 146175.75 | 147174.25 | 146622.5  | 
| 128   | 145625.75 | 143823.25 | 144132 | 144739.25 | 
| 256   | 145380.75 | 147224  | 146203.25 | 147078.75 | 
| 512   | 146045 | 145609.25 | 145149.25 | 144335.5  | 
| 1024  | 148280 | 148076  | 145593.25 | 146534.75 | 
+-------------+-----------+------------+-----------+--------------+ 
| Total  | 1454769 | 1457884.75 | 1454310 | 1456688.75 | 
+-------------+-----------+------------+-----------+--------------+ 

The test script I used can be found here

+1

+1 Questa risposta merita più voti per lo sforzo richiesto per ottenere dati reali. – Jon

+1

Qualche idea su quale versione di SQL Server sono state generate queste statistiche? –

+1

@MartinBrown - IIRC originariamente 2008 anche se recentemente ho rifatto i test nel 2012 per la modifica più recente e ho trovato lo stesso. –

Problemi correlati