Bu yanlış anlamanın nedeni muhtemelen tüm yazıları okumaya son vereceği inancından kaynaklanıyor. Durumun böyle olmadığını görmek kolay.
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'
Plan verir
Bu, SQL Server'ın, dizinin tüm sütunları içermemesine rağmen sonucu kontrol etmek için mevcut en dar dizini kullanabildiğini gösterir. İndeks erişimi yarı birleştirme operatörü altındadır, bu da ilk satır döndürüldüğünde taramayı durdurabileceği anlamına gelir.
Yani yukarıdaki inancın yanlış olduğu açıktır.
Bununla birlikte, Sorgu İyileştirici ekibinden Conor Cunningham, burada tipik SELECT 1
olarak bu durumda kullandığını açıklıyor çünkü bu , sorgu derlemesinde küçük bir performans farkı yaratabilir .
QP *
, boru hattındaki herkesi alıp genişletecek ve bunları nesnelere bağlayacaktır (bu durumda, sütunların listesi). Daha sonra sorgunun doğası gereği gereksiz sütunları kaldıracaktır.
Dolayısıyla, bunun EXISTS
gibi basit bir alt sorgu için :
SELECT col1 FROM MyTable WHERE EXISTS
(SELECT * FROM Table2 WHERE
MyTable.col1=Table2.col2)
*
Bazı potansiyel büyük sütun listesine genişletilecek ve daha sonra belirlenecek Bunun semantik
EXISTS
böylece temelde hepsi kaldırılabilir, bu sütunların herhangi gerektirmez.
"SELECT 1
", sorgu derlemesi sırasında o tablo için gereksiz meta verileri incelemek zorunda kalmayacaktır.
Ancak, çalışma zamanında sorgunun iki biçimi aynı olacak ve aynı çalışma zamanlarına sahip olacaktır.
Bu sorguyu çeşitli sayıda sütun içeren boş bir tabloda ifade etmenin dört olası yolunu test ettim. SELECT 1
vs SELECT *
vs SELECT Primary_Key
vs SELECT Other_Not_Null_Column
.
OPTION (RECOMPILE)
Saniyedeki ortalama yürütme sayısını kullanarak sorguları bir döngüde çalıştırdım ve ölçtüm. Aşağıdaki sonuçlar
+
| 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 |
+
As hiçbir tutarlı arasındaki kazanır görülebilir SELECT 1
ve SELECT *
ve bu ikisi arasındaki fark göz ardı edilebilir yaklaşır. SELECT Not Null col
Ve SELECT PK
biraz daha hızlı olsa görünüyor.
Tablodaki sütun sayısı arttıkça, sorguların dördünün de performansı düşer.
Tablo boş olduğundan, bu ilişki yalnızca sütun meta verilerinin miktarıyla açıklanabilir görünmektedir. Zira, COUNT(1)
bunun COUNT(*)
sürecin bir noktasında aşağıdan yeniden yazıldığını görmek kolaydır .
SET SHOWPLAN_TEXT ON;
GO
SELECT COUNT(1)
FROM master..spt_values
Aşağıdaki planı veren
|
|
|
Aşağıdakileri yürütürken SQL Server sürecine bir hata ayıklayıcı ekleme ve rastgele kırma
DECLARE @V int
WHILE (1=1)
SELECT @V=1 WHERE EXISTS (SELECT 1 FROM
Tablonun çoğu zaman 1.024 sütuna sahip olduğu durumlarda, çağrı yığınının aşağıdaki gibi göründüğünü buldum, bu da gerçekten SELECT 1
kullanıldığında bile zaman yükleme sütunu meta verilerinin büyük bir bölümünü harcadığını gösterir ( tablonun 1 sütunu var rastgele kırma 10 denemede çağrı yığınının bu bitine ulaşmadı)
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
Bu manuel profil oluşturma girişimi, iki durum için derleme süresini tüketen çok farklı bir işlev seçimi gösteren VS 2012 kod profilcisi tarafından desteklenmektedir ( İlk 15 İşlev 1024 sütun ve İlk 15 İşlev 1 sütun ).
Hem ve SELECT 1
hem de SELECT *
sürümler sütun izinlerini kontrol eder ve kullanıcıya tablodaki tüm sütunlara erişim izni verilmezse başarısız olur.
Yığın üzerindeki bir sohbetten aldığım bir örnek
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);
GO
REVERT;
DROP USER blat
DROP TABLE T
Bu nedenle, kullanım sırasındaki küçük belirgin farkın SELECT some_not_null_col
yalnızca o belirli sütundaki izinleri kontrol etmesidir (yine de herkes için meta verileri yükler). Ancak, temel tablodaki sütun sayısı arttıkça bir şey küçülürse, iki yaklaşım arasındaki yüzde farkı gerçeklere uymuyor gibi görünmektedir.
Her halükarda acele etmeyeceğim ve tüm sorgularımı bu forma değiştirmeyeceğim çünkü fark çok küçük ve yalnızca sorgu derleme sırasında belirgindir. OPTION (RECOMPILE)
Sonraki yürütmelerin önbelleğe alınmış bir planı kullanabilmesi için kaldırılması aşağıdakileri verdi.
+
| 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 |
+
Kullandığım test komut dosyası burada bulunabilir