Saya memiliki kelas pertanyaan yang menguji keberadaan satu dari dua hal. Itu adalah bentuk
SELECT CASE
WHEN EXISTS (SELECT 1 FROM ...)
OR EXISTS (SELECT 1 FROM ...)
THEN 1 ELSE 0 END;
Pernyataan aktual dihasilkan dalam C dan dieksekusi sebagai permintaan ad-hoc melalui koneksi ODBC.
Baru-baru ini terungkap bahwa SELECT kedua mungkin akan lebih cepat daripada SELECT pertama dalam banyak kasus dan bahwa mengganti urutan kedua klausa EXISTS menyebabkan percepatan yang drastis dalam setidaknya satu kasus uji kasar yang baru saja kita buat.
Hal yang jelas untuk dilakukan adalah langsung beralih dua klausa, tapi saya ingin melihat apakah seseorang yang lebih akrab dengan SQL Server akan peduli untuk mempertimbangkan ini. Rasanya seperti saya mengandalkan kebetulan dan "detail implementasi".
(Sepertinya SQL Server lebih pintar, itu akan mengeksekusi kedua klausa EXISTS secara paralel dan membiarkan salah satu yang pertama menyelesaikan hubungan pendek yang lain.)
Apakah ada cara yang lebih baik untuk mendapatkan SQL Server untuk secara konsisten meningkatkan waktu berjalan dari permintaan seperti itu?
Memperbarui
Terima kasih atas waktu dan minat Anda pada pertanyaan saya. Saya tidak mengharapkan pertanyaan tentang rencana kueri yang sebenarnya, tetapi saya bersedia membagikannya.
Ini untuk komponen perangkat lunak yang mendukung SQL Server 2008R2 dan lebih tinggi. Bentuk data bisa sangat berbeda tergantung pada konfigurasi dan penggunaan. Rekan kerja saya berpikir untuk melakukan perubahan pada kueri ini karena (dalam contoh) dbf_1162761$z$rv$1257927703
tabel akan selalu memiliki lebih besar atau sama dengan jumlah baris di dalamnya daripada dbf_1162761$z$dd$1257927703
tabel - kadang-kadang secara signifikan lebih banyak (urutan besarnya).
Ini kasus kasar yang saya sebutkan. Permintaan pertama adalah yang lambat dan memakan waktu sekitar 20 detik. Kueri kedua selesai dalam sekejap.
Untuk apa nilainya, bit "MENGOPTIMALKAN UNTUK TIDAK DIKETAHUI" juga ditambahkan baru-baru ini karena parameter sniffing merusak kasus tertentu.
Permintaan asli:
SELECT CASE
WHEN EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$rv$1257927703 rv INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=rv.txid WHERE tx.generation BETWEEN 1500 AND 2502)
OR EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$dd$1257927703 dd INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=dd.txid WHERE tx.generation BETWEEN 1500 AND 2502)
THEN 1 ELSE 0 END
OPTION (OPTIMIZE FOR UNKNOWN)
Rencana asli:
|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))
|--Nested Loops(Left Semi Join, DEFINE:([Expr1007] = [PROBE VALUE]))
|--Constant Scan
|--Concatenation
|--Nested Loops(Inner Join, WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]))
| |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[PK__dbf_1162__97770A2F62EEAE79] AS [rv]), WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]>(0)))
| |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[gendex] AS [tx]), SEEK:([tx].[generation] >= (1500) AND [tx].[generation] <= (2502)) ORDERED FORWARD)
|--Nested Loops(Inner Join, OUTER REFERENCES:([tx].[txid]))
|--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[PK__dbf_1162__E3BA953EC2197789] AS [tx]), WHERE:([scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]>=(1500) AND [scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]<=(2502)) ORDERED FORWARD)
|--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[n$dbf_1162761$z$dd$txid$1257927703] AS [dd]), SEEK:([dd].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]), WHERE:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[txid] as [dd].[txid]>(0)) ORDERED FORWARD)
Memperbaiki kueri:
SELECT CASE
WHEN EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$dd$1257927703 dd INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=dd.txid WHERE tx.generation BETWEEN 1500 AND 2502)
OR EXISTS (SELECT 1 FROM zumero.dbf_1162761$z$rv$1257927703 rv INNER JOIN zumero.dbf_1162761$t$tx tx ON tx.txid=rv.txid WHERE tx.generation BETWEEN 1500 AND 2502)
THEN 1 ELSE 0 END
OPTION (OPTIMIZE FOR UNKNOWN)
Rencana tetap:
|--Compute Scalar(DEFINE:([Expr1006]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))
|--Nested Loops(Left Semi Join, DEFINE:([Expr1007] = [PROBE VALUE]))
|--Constant Scan
|--Concatenation
|--Nested Loops(Inner Join, OUTER REFERENCES:([tx].[txid]))
| |--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[PK__dbf_1162__E3BA953EC2197789] AS [tx]), WHERE:([scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]>=(1500) AND [scale].[zumero].[dbf_1162761$t$tx].[generation] as [tx].[generation]<=(2502)) ORDERED FORWARD)
| |--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[n$dbf_1162761$z$dd$txid$1257927703] AS [dd]), SEEK:([dd].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]), WHERE:([scale].[zumero].[dbf_1162761$z$dd$1257927703].[txid] as [dd].[txid]>(0)) ORDERED FORWARD)
|--Nested Loops(Inner Join, WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]=[scale].[zumero].[dbf_1162761$t$tx].[txid] as [tx].[txid]))
|--Clustered Index Scan(OBJECT:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[PK__dbf_1162__97770A2F62EEAE79] AS [rv]), WHERE:([scale].[zumero].[dbf_1162761$z$rv$1257927703].[txid] as [rv].[txid]>(0)))
|--Index Seek(OBJECT:([scale].[zumero].[dbf_1162761$t$tx].[gendex] AS [tx]), SEEK:([tx].[generation] >= (1500) AND [tx].[generation] <= (2502)) ORDERED FORWARD)
sumber
Jawaban:
Sebagai aturan umum, SQL Server akan mengeksekusi bagian-bagian dari
CASE
pernyataan secara berurutan tetapi bebas untuk menyusun ulangOR
kondisi. Untuk beberapa kueri, Anda bisa mendapatkan kinerja yang lebih baik secara konsisten dengan mengubah urutanWHEN
ekspresi di dalamCASE
pernyataan. Terkadang Anda juga bisa mendapatkan kinerja yang lebih baik ketika mengubah urutan kondisi dalam sebuahOR
pernyataan, tetapi itu bukan perilaku yang dijamin.Mungkin lebih baik untuk menjalaninya dengan contoh sederhana. Saya sedang menguji terhadap SQL Server 2016 sehingga mungkin Anda tidak akan mendapatkan hasil yang sama persis di komputer Anda, tetapi sejauh yang saya tahu prinsip yang sama berlaku. Pertama saya akan menempatkan satu juta bilangan bulat dari 1 hingga 10.000 di dua tabel, satu dengan indeks berkerumun dan satu sebagai tumpukan:
Pertimbangkan pertanyaan berikut:
Kita tahu bahwa mengevaluasi subquery terhadap
X_CI
akan jauh lebih murah daripada subquery terhadapX_HEAP
, terutama ketika tidak ada baris yang cocok. Jika tidak ada baris yang cocok maka kita hanya perlu melakukan beberapa pembacaan logis terhadap tabel dengan indeks berkerumun. Namun, kita perlu memindai semua baris tumpukan untuk mengetahui bahwa tidak ada baris yang cocok. Pengoptimal juga mengetahui hal ini. Secara umum, menggunakan indeks berkerumun untuk mencari satu baris sangat murah dibandingkan dengan memindai tabel.Untuk contoh data ini saya akan menulis kueri seperti ini:
Itu secara efektif memaksa SQL Server untuk menjalankan subquery terhadap tabel dengan indeks berkerumun terlebih dahulu. Inilah hasil dari
SET STATISTICS IO, TIME ON
:Melihat rencana permintaan, jika pencarian di label 1 mengembalikan data apa pun selain pemindaian di label 2 tidak diperlukan dan tidak akan terjadi:
Permintaan berikut ini jauh lebih efisien:
Melihat rencana permintaan, kami melihat bahwa pemindaian pada label 2 selalu terjadi. Jika baris ditemukan maka pencarian di label 1 dilewati. Itu bukan urutan yang kami inginkan:
Hasil kinerja kembali yang naik:
Kembali ke permintaan asli, untuk permintaan ini saya melihat pencarian dan pemindaian dievaluasi dalam urutan yang baik untuk kinerja:
Dan dalam kueri ini mereka dievaluasi dalam urutan yang berlawanan:
Namun, tidak seperti pasangan kueri sebelumnya, tidak ada yang memaksa pengoptimal permintaan SQL Server untuk mengevaluasi satu sebelum yang lain. Anda seharusnya tidak mengandalkan perilaku itu untuk hal-hal penting.
Kesimpulannya, jika Anda memerlukan satu subquery untuk dievaluasi sebelum yang lain maka gunakan
CASE
pernyataan atau metode lain untuk memaksa pemesanan. Jika tidak, silakan memesan subquery dalamOR
kondisi sesuai keinginan Anda, tetapi ketahuilah bahwa tidak ada jaminan bahwa pengoptimal akan menjalankannya dalam urutan seperti yang tertulis.Tambahan:
Pertanyaan tindak lanjut alami adalah apa yang dapat Anda lakukan jika Anda ingin SQL Server untuk memutuskan permintaan mana yang lebih murah dan untuk mengeksekusi yang pertama? Semua metode sejauh ini tampaknya diimplementasikan oleh SQL Server dalam urutan permintaan ditulis, bahkan jika itu tidak dijamin perilaku untuk sebagian dari mereka.
Berikut adalah salah satu opsi yang tampaknya berfungsi untuk tabel demo sederhana:
Anda dapat menemukan demo biola db di sini . Mengubah urutan tabel yang diturunkan tidak mengubah rencana kueri. Di kedua kueri
X_HEAP
tabel tidak tersentuh. Dengan kata lain, pengoptimal permintaan muncul untuk menjalankan permintaan yang lebih murah terlebih dahulu. Saya tidak bisa merekomendasikan menggunakan sesuatu seperti ini dalam produksi jadi ini di sini untuk sebagian besar nilai rasa ingin tahu Mungkin ada cara yang jauh lebih sederhana untuk mencapai hal yang sama.sumber
CASE WHEN EXISTS (SELECT 1 FROM dbo.X_CI WHERE ID = 500000 UNION ALL SELECT 1 FROM dbo.X_HEAP WHERE ID = 500000) THEN 1 ELSE 0 END
bisa menjadi alternatif, meskipun itu masih bergantung pada manual memutuskan permintaan mana yang lebih cepat dan menempatkan yang pertama. Saya tidak yakin apakah ada cara untuk mengekspresikannya sehingga SQL Server akan secara otomatis memesan ulang sehingga yang murah secara otomatis dievaluasi terlebih dahulu.