Alasan kesalahpahaman ini mungkin karena keyakinan bahwa ia akan membaca semua kolom. Sangat mudah untuk melihat bahwa bukan ini masalahnya.
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'
Memberikan rencana
Ini menunjukkan bahwa SQL Server dapat menggunakan indeks tersempit yang tersedia untuk memeriksa hasil meskipun pada kenyataannya indeks tidak menyertakan semua kolom. Akses indeks berada di bawah operator semi-join yang berarti dapat menghentikan pemindaian segera setelah baris pertama dikembalikan.
Jadi jelas sekali keyakinan di atas salah.
Namun Conor Cunningham dari tim Pengoptimal Kueri menjelaskan di sini bahwa ia biasanya menggunakan SELECT 1
dalam kasus ini karena dapat membuat perbedaan kinerja kecil dalam kompilasi kueri.
QP akan mengambil dan memperluas semua yang ada *
di awal pipeline dan mengikatnya ke objek (dalam hal ini, daftar kolom). Ini kemudian akan menghapus kolom yang tidak dibutuhkan karena sifat kueri.
Jadi untuk EXISTS
subkueri sederhana seperti ini:
SELECT col1 FROM MyTable WHERE EXISTS
(SELECT * FROM Table2 WHERE
MyTable.col1=Table2.col2)
Ini *
akan diperluas ke beberapa daftar kolom yang berpotensi besar dan kemudian akan ditentukan bahwa semantik
EXISTS
tidak memerlukan salah satu kolom tersebut, jadi pada dasarnya semuanya dapat dihapus.
" SELECT 1
" akan menghindari pemeriksaan metadata yang tidak diperlukan untuk tabel itu selama kompilasi kueri.
Namun, pada waktu proses, kedua bentuk kueri tersebut akan sama dan akan memiliki waktu proses yang identik.
Saya menguji empat kemungkinan cara untuk mengekspresikan kueri ini pada tabel kosong dengan berbagai jumlah kolom. SELECT 1
vs SELECT *
vs SELECT Primary_Key
vs SELECT Other_Not_Null_Column
.
Saya menjalankan kueri dalam satu putaran menggunakan OPTION (RECOMPILE)
dan mengukur jumlah rata-rata eksekusi per detik. Hasil di bawah
+
| 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 |
+
Seperti yang dapat dilihat, tidak ada pemenang yang konsisten antara SELECT 1
dan SELECT *
dan perbedaan antara kedua pendekatan dapat diabaikan. The SELECT Not Null col
dan SELECT PK
tampil sedikit lebih cepat sekalipun.
Keempat kueri tersebut menurunkan kinerja saat jumlah kolom dalam tabel meningkat.
Karena tabel kosong, hubungan ini tampaknya hanya dapat dijelaskan dengan jumlah metadata kolom. Karena COUNT(1)
mudah untuk melihat bahwa ini akan ditulis ulang COUNT(*)
di beberapa titik dalam proses dari bawah.
SET SHOWPLAN_TEXT ON;
GO
SELECT COUNT(1)
FROM master..spt_values
Yang memberikan rencana berikut
|
|
|
Melampirkan debugger ke proses SQL Server dan secara acak melanggar saat menjalankan di bawah ini
DECLARE @V int
WHILE (1=1)
SELECT @V=1 WHERE EXISTS (SELECT 1 FROM
Saya menemukan bahwa dalam kasus di mana tabel memiliki 1.024 kolom sebagian besar waktu tumpukan panggilan terlihat seperti di bawah ini menunjukkan bahwa memang menghabiskan sebagian besar waktu memuat metadata kolom bahkan ketika SELECT 1
digunakan (Untuk kasus di mana tabel memiliki 1 kolom yang secara acak melanggar tidak mencapai sedikit tumpukan panggilan ini dalam 10 upaya)
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
Upaya pembuatan profil manual ini didukung oleh profiler kode VS 2012 yang menunjukkan pilihan fungsi yang sangat berbeda yang memakan waktu kompilasi untuk dua kasus ( kolom 15 Fungsi Teratas 1024 vs kolom 15 Fungsi Teratas 1 ).
Baik versi SELECT 1
dan SELECT *
akhirnya memeriksa izin kolom dan gagal jika pengguna tidak diberikan akses ke semua kolom dalam tabel.
Contoh yang saya kutip dari percakapan di 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);
GO
REVERT;
DROP USER blat
DROP TABLE T
Jadi orang mungkin berspekulasi bahwa perbedaan kecil yang terlihat saat menggunakan SELECT some_not_null_col
adalah bahwa itu hanya berakhir memeriksa izin pada kolom tertentu (meskipun masih memuat metadata untuk semua). Namun ini tampaknya tidak sesuai dengan fakta karena perbedaan persentase antara kedua pendekatan jika ada yang semakin kecil karena jumlah kolom dalam tabel yang mendasarinya meningkat.
Bagaimanapun saya tidak akan terburu-buru dan mengubah semua pertanyaan saya ke formulir ini karena perbedaannya sangat kecil dan hanya terlihat selama kompilasi kueri. Menghapus OPTION (RECOMPILE)
sehingga eksekusi selanjutnya dapat menggunakan rencana cache memberi berikut ini.
+
| 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 |
+
Skrip pengujian yang saya gunakan dapat ditemukan di sini