Unterabfrage mit Exists 1 oder Exists *


88

Ich habe meine EXISTS-Schecks wie folgt geschrieben:

IF EXISTS (SELECT * FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Where Columns=@Filters
END

Einer der Datenbankadministratoren in einem früheren Leben sagte mir, wenn ich eine EXISTSKlausel mache , benutze SELECT 1stattSELECT *

IF EXISTS (SELECT 1 FROM TABLE WHERE Columns=@Filters)
BEGIN
   UPDATE TABLE SET ColumnsX=ValuesX WHERE Columns=@Filters
END

Macht das wirklich einen Unterschied?


1
Sie haben EXISTS vergessen (SELECT NULL FROM ...). Dies wurde vor kurzem übrigens
OMG Ponies

16
ps bekommen einen neuen DBA. Aberglaube hat keinen Platz in der IT, insbesondere in der Datenbankverwaltung (von einem ehemaligen DBA !!!)
Matt Rogish

Antworten:


135

Nein, SQL Server ist intelligent und weiß, dass es für EXISTS verwendet wird, und gibt KEINE DATEN an das System zurück.

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

Die Auswahlliste einer von EXISTS eingeführten Unterabfrage besteht fast immer aus einem Sternchen (*). Es gibt keinen Grund, Spaltennamen aufzulisten, da Sie nur testen, ob Zeilen vorhanden sind, die die in der Unterabfrage angegebenen Bedingungen erfüllen.

Führen Sie Folgendes aus, um sich selbst zu überprüfen:

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

Wenn es tatsächlich etwas mit der SELECT-Liste machen würde, würde es einen div by zero-Fehler auslösen. Das tut es nicht.

BEARBEITEN: Beachten Sie, dass der SQL-Standard tatsächlich darüber spricht.

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

3) Fall:
a) Wenn das <select list>"*" einfach in einem enthalten <subquery> ist, das unmittelbar in einem enthalten ist <exists predicate>, dann <select list> ist das äquivalent zu einem <value expression> , das willkürlich ist <literal>.


1
Der EXISTSTrick mit 1/0 kann sogar darauf ausgedehnt werden SELECT 1 WHERE EXISTS(SELECT 1/0)... scheint ein Schritt abstrakter zu sein, als der zweite SELECTkeine FROMKlausel hat
warum theq

1
@whytheq - Or SELECT COUNT(*) WHERE EXISTS(SELECT 1/0). Ein SELECTohne FROMin SQL Server wird so behandelt, als würde auf eine einzelne dualZeilentabelle zugegriffen (z. B. ähnlich wie bei der Auswahl aus der Tabelle in anderen RDBMS)
Martin Smith,

@MartinSmith Prost - also der Punkt ist, dass SELECTeine 1-Zeilen-Tabelle erstellt wird, bevor etwas anderes getan wird, obwohl der 1/0Müll die 1-Zeilen-Tabelle noch ist EXISTS?
Whytheq

War dies schon immer der Fall oder handelt es sich um eine Optimierung, die in einer bestimmten Version von SQL Server eingeführt wurde?
Martin Brown

1
@ MartinSmith bis "quoth". Danke, dass du es wieder repariert hast.
Gurwinder Singh

111

Der Grund für dieses Missverständnis liegt vermutlich in der Annahme, dass am Ende alle Spalten gelesen werden. Es ist leicht zu erkennen, dass dies nicht der Fall ist.

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'

Gibt Plan

Planen

Dies zeigt, dass SQL Server den engsten verfügbaren Index verwenden konnte, um das Ergebnis zu überprüfen, obwohl der Index nicht alle Spalten enthält. Der Indexzugriff erfolgt unter einem Semi-Join-Operator. Dies bedeutet, dass der Scanvorgang beendet werden kann, sobald die erste Zeile zurückgegeben wird.

Es ist also klar, dass der obige Glaube falsch ist.

Conor Cunningham vom Query Optimiser-Team erklärt hier jedoch, dass er SELECT 1in diesem Fall normalerweise verwendet , da dies einen geringfügigen Leistungsunterschied bei der Kompilierung der Abfrage bewirken kann .

Der QP nimmt und erweitert alle *früh in der Pipeline und bindet sie an Objekte (in diesem Fall die Liste der Spalten). Aufgrund der Art der Abfrage werden dann nicht benötigte Spalten entfernt.

Für eine einfache EXISTSUnterabfrage wie diese:

SELECT col1 FROM MyTable WHERE EXISTS (SELECT * FROM Table2 WHERE MyTable.col1=Table2.col2)Das *wird auf eine potenziell große Spaltenliste erweitert, und dann wird festgestellt, dass für die Semantik des EXISTSkeine dieser Spalten erforderlich ist, sodass im Grunde alle entfernt werden können.

" SELECT 1" vermeidet, dass nicht benötigte Metadaten für diese Tabelle während der Abfragekompilierung überprüft werden müssen.

Zur Laufzeit sind die beiden Formen der Abfrage jedoch identisch und haben identische Laufzeiten.

Ich habe vier Möglichkeiten getestet, diese Abfrage in einer leeren Tabelle mit verschiedenen Spaltenzahlen auszudrücken. SELECT 1vs SELECT *vs SELECT Primary_Keyvs SELECT Other_Not_Null_Column.

Ich habe die Abfragen in einer Schleife ausgeführt OPTION (RECOMPILE)und die durchschnittliche Anzahl von Ausführungen pro Sekunde gemessen. Ergebnisse unten

Geben Sie hier die Bildbeschreibung ein

+-------------+----------+---------+---------+--------------+
| 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        |
+-------------+----------+---------+---------+--------------+

Wie zu sehen ist, gibt es keinen konsistenten Gewinner zwischen SELECT 1und SELECT *und der Unterschied zwischen den beiden Ansätzen ist vernachlässigbar. Die SELECT Not Null colund SELECT PKerscheinen allerdings etwas schneller.

Alle vier Abfragen beeinträchtigen die Leistung, wenn die Anzahl der Spalten in der Tabelle zunimmt.

Da die Tabelle leer ist, scheint diese Beziehung nur durch die Anzahl der Spaltenmetadaten erklärbar zu sein. Denn COUNT(1)es ist leicht zu erkennen, dass dies COUNT(*)irgendwann im Prozess von unten umgeschrieben wird.

SET SHOWPLAN_TEXT ON;

GO

SELECT COUNT(1)
FROM master..spt_values

Welches gibt den folgenden Plan

  |--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]))

Anhängen eines Debuggers an den SQL Server-Prozess und zufälliges Unterbrechen während der Ausführung des folgenden Vorgangs

DECLARE @V int 

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

Ich fand heraus, dass in den Fällen, in denen die Tabelle die meiste Zeit 1.024 Spalten enthält, der Aufrufstapel wie folgt aussieht, was darauf hinweist, dass er tatsächlich einen großen Teil der Zeit damit verbringt, Spaltenmetadaten zu laden, selbst wenn er SELECT 1verwendet wird (für den Fall, dass der Tabelle hat 1 Spalte zufällig gebrochen hat dieses Bit des Aufrufstapels in 10 Versuchen nicht getroffen)

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
kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes 

Dieses Handbuch Profilierungsversuch wird durch den VS 2012 Code - Profiler , die zeigt , eine ganz andere Auswahl von Funktionen raubend , die Kompilierung für die beiden Fälle (gesichert Top 15 Funktionen 1024 Spalten vs Top 15 Funktionen 1 Spalte ).

Sowohl die SELECT 1als auch die SELECT *Versionen überprüfen die Spaltenberechtigungen und schlagen fehl, wenn dem Benutzer nicht Zugriff auf alle Spalten in der Tabelle gewährt wird.

Ein Beispiel, das ich aus einem Gespräch auf dem Haufen herausgeschnitten habe

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

Man könnte also spekulieren, dass der geringfügige offensichtliche Unterschied bei der Verwendung darin SELECT some_not_null_colbesteht, dass nur die Berechtigungen für diese bestimmte Spalte überprüft werden (obwohl immer noch die Metadaten für alle geladen werden). Dies scheint jedoch nicht mit den Fakten übereinzustimmen, da die prozentuale Differenz zwischen den beiden Ansätzen kleiner wird, wenn die Anzahl der Spalten in der zugrunde liegenden Tabelle zunimmt.

Auf jeden Fall werde ich nicht alle meine Abfragen in dieses Formular ändern, da der Unterschied sehr gering ist und nur während der Abfragekompilierung erkennbar ist. Das Entfernen des, OPTION (RECOMPILE)damit nachfolgende Ausführungen einen zwischengespeicherten Plan verwenden können, ergab Folgendes.

Geben Sie hier die Bildbeschreibung ein

+-------------+-----------+------------+-----------+--------------+
| 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   |
+-------------+-----------+------------+-----------+--------------+

Das von mir verwendete Testskript finden Sie hier


3
+1 Diese Antwort verdient mehr Stimmen für den Aufwand, um echte Daten zu erhalten.
Jon

1
Irgendeine Idee, auf welcher Version von SQL Server diese Statistiken generiert wurden?
Martin Brown

3
@MartinBrown - IIRC ursprünglich 2008, obwohl ich die Tests vor kurzem für 2012 für die letzte Bearbeitung überarbeitet und das gleiche gefunden habe.
Martin Smith

8

Der beste Weg, dies zu wissen, besteht darin, beide Versionen auf Leistung zu testen und den Ausführungsplan für beide Versionen zu überprüfen. Wählen Sie eine Tabelle mit vielen Spalten.


2
+1. Keine Ahnung, warum dies abgelehnt wurde. Ich dachte immer, es sei besser, einem Mann das Fischen beizubringen, als ihm nur einen Fisch zu geben. Wie werden die Leute etwas lernen?
Oger Psalm33

5

Es gibt keinen Unterschied in SQL Server und es war nie ein Problem in SQL Server. Der Optimierer weiß, dass sie gleich sind. Wenn Sie sich die Ausführungspläne ansehen, werden Sie feststellen, dass sie identisch sind.


1

Persönlich fällt es mir sehr, sehr schwer zu glauben, dass sie nicht auf denselben Abfrageplan optimieren. Die einzige Möglichkeit, in Ihrer speziellen Situation Bescheid zu wissen, besteht darin, sie zu testen. Wenn Sie dies tun, melden Sie sich bitte zurück!


-1

Kein wirklicher Unterschied, aber es könnte einen sehr kleinen Leistungseinbruch geben. Als Faustregel sollten Sie nicht mehr Daten anfordern, als Sie benötigen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.