JIKA ADA membutuhkan waktu lebih lama dari pernyataan pilihan tertanam

35

Ketika saya menjalankan kode berikut ini, dibutuhkan 22,5 menit dan membaca 106 juta. Namun, jika saya menjalankan hanya pernyataan pilih dalam dengan sendirinya hanya membutuhkan waktu 15 detik dan membaca 264k. Sebagai catatan tambahan, kueri pemilihan tidak mengembalikan catatan.

Adakah yang tahu mengapa itu IF EXISTSakan membuatnya berjalan lebih lama dan melakukan lebih banyak bacaan? Saya juga mengubah pernyataan pilih untuk dilakukan SELECT TOP 1 [dlc].[id]dan saya membunuhnya setelah 2 menit.

Sebagai perbaikan sementara, saya telah mengubahnya untuk melakukan penghitungan (*) dan menetapkan nilai itu ke variabel @cnt. Kemudian ia membuat IF 0 <> @cntpernyataan. Tapi saya pikir EXISTSakan lebih baik, karena jika ada catatan yang dikembalikan dalam pernyataan pilih itu akan berhenti melakukan pemindaian / mencari setelah menemukan setidaknya satu catatan, sedangkan count(*)akan menyelesaikan permintaan penuh. Apa yang saya lewatkan?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END
Chris Woods
sumber
4
Untuk menghindari masalah sasaran baris, ide lain (belum diuji, ingatlah!) Mungkin untuk mencoba kebalikannya - IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END.
Aaron Bertrand

Jawaban:

32

Adakah yang tahu mengapa itu IF EXISTSakan membuatnya berjalan lebih lama dan melakukan lebih banyak bacaan? Saya juga mengubah pernyataan pilih untuk dilakukan SELECT TOP 1 [dlc].[id]dan saya membunuhnya setelah 2 menit.

Seperti yang saya jelaskan dalam jawaban saya untuk pertanyaan terkait ini:

Bagaimana (dan mengapa) TOP mempengaruhi rencana eksekusi?

Menggunakan EXISTSmemperkenalkan tujuan baris, di mana pengoptimal menghasilkan rencana eksekusi yang bertujuan untuk menemukan baris pertama dengan cepat. Dalam melakukan ini, diasumsikan bahwa data terdistribusi secara seragam. Misalnya, jika statistik menunjukkan ada 100 pertandingan yang diharapkan dalam 100.000 baris, itu akan menganggap itu harus membaca hanya 1.000 baris untuk menemukan pertandingan pertama.

Ini akan menghasilkan waktu eksekusi yang lebih lama dari yang diharapkan jika asumsi ini ternyata salah. Misalnya, jika SQL Server memilih metode akses (mis. Pemindaian tidak berurutan) yang terjadi untuk menemukan nilai pencocokan pertama sangat terlambat dalam pencarian, itu bisa menghasilkan pemindaian yang hampir lengkap. Di sisi lain, jika baris yang cocok ditemukan di antara beberapa baris pertama, kinerja akan sangat baik. Ini adalah risiko mendasar dengan sasaran baris - kinerja yang tidak konsisten.

Sebagai perbaikan sementara, saya telah mengubahnya untuk melakukan penghitungan (*) dan menetapkan nilai itu ke variabel

Biasanya dimungkinkan untuk merumuskan ulang kueri sedemikian rupa sehingga sasaran baris tidak ditetapkan. Tanpa sasaran baris, kueri masih dapat berakhir ketika baris pertama yang cocok ditemukan (jika ditulis dengan benar), tetapi strategi rencana eksekusi kemungkinan akan berbeda (dan mudah-mudahan, lebih efektif). Jelas, hitung (*) akan membutuhkan membaca semua baris, jadi itu bukan alternatif yang sempurna.

Jika Anda menjalankan SQL Server 2008 R2 atau yang lebih baru, Anda juga dapat secara umum menggunakan flag jejak yang didokumentasikan dan didukung 4138 untuk mendapatkan rencana eksekusi tanpa sasaran baris. Bendera ini juga dapat ditentukan menggunakan petunjuk yang didukung OPTION (QUERYTRACEON 4138) , meskipun perlu diketahui bahwa ini memerlukan izin sysadmin runtime , kecuali digunakan dengan panduan paket.

Sayangnya

Tidak satu pun di atas yang berfungsi dengan IF EXISTSpernyataan bersyarat. Ini hanya berlaku untuk DML biasa. Ini akan bekerja dengan SELECT TOP (1)formulasi alternatif yang Anda coba. Itu mungkin lebih baik daripada menggunakan COUNT(*), yang harus menghitung semua baris yang memenuhi syarat, seperti yang disebutkan sebelumnya.

Yang mengatakan, ada sejumlah cara untuk mengekspresikan persyaratan ini yang akan memungkinkan Anda untuk menghindari atau mengendalikan tujuan baris, sambil menghentikan pencarian lebih awal. Satu contoh terakhir:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;
Paul White mengatakan GoFundMonica
sumber
Contoh alt yang Anda berikan berlari dalam 3,75 menit dan menghasilkan 46 juta bacaan. Jadi, sementara lebih cepat dari permintaan asli saya, saya pikir dalam hal ini saya akan tetap dengan @ cnt = hitung (*) dan mengevaluasi variabel sesudahnya. Terutama karena 99% dari waktu ini berjalan tidak akan ada apa-apa di dalamnya. Kedengarannya seperti berdasarkan jawaban Anda dan Rob bahwa Ada hanya baik jika Anda benar-benar mengharapkan semacam hasil dan hasil itu didistribusikan secara merata dalam data Anda.
Chris Woods
3
@ChrisWoods: Anda berkata "Terutama karena 99% dari waktu ini berjalan tidak akan ada apa-apa di dalamnya". Ini cukup banyak menjamin bahwa tujuan baris seseorang adalah ide yang buruk, karena Anda mengharapkan biasanya TIDAK ada baris, dan harus memindai semuanya untuk menemukan bahwa tidak ada. Jika Anda tidak dapat menambahkan beberapa indeks pintar, tetap dengan COUNT (*).
Ross Presser
25

Karena EXIS hanya perlu menemukan satu baris, itu akan menggunakan tujuan baris. Ini terkadang menghasilkan rencana yang kurang ideal. Jika Anda mengharapkannya akan seperti itu untuk Anda, isi sebuah variabel dengan hasil dari COUNT(*)dan kemudian uji variabel itu untuk melihat apakah itu lebih dari 0.

Jadi ... Dengan sasaran baris kecil, ia akan menghindari operasi pemblokiran, seperti membangun tabel hash, atau menyortir aliran yang dapat berguna untuk menggabungkan gabungan, karena akan mengetahui bahwa ia terikat untuk menemukan sesuatu dengan sangat cepat, dan oleh karena itu loop bersarang akan lebih baik jika ia menemukan sesuatu. Kecuali bahwa ini dapat membuat rencana yang jauh lebih buruk di seluruh set. Jika menemukan satu baris cepat, Anda ingin metode ini untuk menghindari blok ...

Rob Farley
sumber