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
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 1
in 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 EXISTS
Unterabfrage 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
EXISTS
keine 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 1
vs SELECT *
vs SELECT Primary_Key
vs 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
+-------------+----------+---------+---------+--------------+
| 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 1
und SELECT *
und der Unterschied zwischen den beiden Ansätzen ist vernachlässigbar. Die SELECT Not Null col
und SELECT PK
erscheinen 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 1
verwendet 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 1
als 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_col
besteht, 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.
+-------------+-----------+------------+-----------+--------------+
| 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