PILIHAN (RECOMPILE) Selalu Lebih Cepat; Mengapa?

169

Saya mengalami situasi aneh di mana menambahkan OPTION (RECOMPILE)permintaan saya menyebabkannya berjalan dalam setengah detik, sementara menghilangkannya menyebabkan permintaan mengambil lebih dari lima menit.

Ini adalah kasus ketika kueri dieksekusi dari Query Analyzer atau dari program C # saya via SqlCommand.ExecuteReader(). Memanggil (atau tidak menelepon) DBCC FREEPROCCACHEatau DBCC dropcleanbufferstidak ada bedanya; Hasil kueri selalu dikembalikan secara instan dengan OPTION (RECOMPILE)dan lebih dari lima menit tanpanya. Kueri selalu dipanggil dengan parameter yang sama [demi pengujian ini].

Saya menggunakan SQL Server 2008.

Saya cukup nyaman dengan menulis SQL tetapi tidak pernah menggunakan OPTIONperintah dalam permintaan sebelumnya dan tidak terbiasa dengan seluruh konsep cache rencana sampai memindai posting di forum ini. Pemahaman saya dari posting adalah bahwa itu OPTION (RECOMPILE)adalah operasi yang mahal. Tampaknya menciptakan strategi pencarian baru untuk kueri. Jadi mengapa kemudian, bahwa pertanyaan selanjutnya yang menghilangkan OPTION (RECOMPILE)begitu lambat? Tidakkah seharusnya pertanyaan selanjutnya menggunakan strategi pencarian yang dihitung pada panggilan sebelumnya yang termasuk petunjuk kompilasi?

Apakah sangat tidak biasa memiliki permintaan yang memerlukan petunjuk kompilasi pada setiap panggilan tunggal?

Maaf untuk pertanyaan entry-level tapi saya tidak bisa benar-benar membuat kepala atau ekor dari ini.

UPDATE: Saya telah diminta untuk mengirim kueri ...

select acctNo,min(date) earliestDate 
from( 
    select acctNo,tradeDate as date 
    from datafeed_trans 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_money 
    where feedid=@feedID and feedDate=@feedDate 

    union 

    select acctNo,feedDate as date 
    from datafeed_jnl 
    where feedid=@feedID and feedDate=@feedDate 
)t1 
group by t1.acctNo
OPTION(RECOMPILE)

Saat menjalankan tes dari Query Analyzer, saya menambahkan baris berikut:

declare @feedID int
select @feedID=20

declare @feedDate datetime
select @feedDate='1/2/2009'

Saat memanggilnya dari program C # saya, parameter dilewatkan melalui SqlCommand.Parametersproperti.

Untuk keperluan diskusi ini, Anda dapat mengasumsikan bahwa parameter tidak pernah berubah sehingga kami dapat mengesampingkan parameter yang berbau tidak optimal sebagai penyebabnya.

Chad Decker
sumber
3
Apa parameter untuk kueri? Lihat artikel ini. blogs.msdn.com/b/turgays/archive/2013/09/10/... Pada dasarnya, SQL berupaya membuat rencana kueri berdasarkan parameter saat proc pertama kali dikompilasi. Mungkin menghasilkan rencana yang tidak optimal ketika Anda mulai melewati parameter yang berbeda, mungkin lebih realistis
Sparky
3
Apakah kueri cukup ringkas untuk dicantumkan di sini? Saya pikir Sparky benar dan itu mungkin terkait dengan parameter mengendus, saya punya masalah yang sama yang bingung sih dari saya sampai membaca artikel ini sangat baik: sommarskog.se/query-plan-mysteries.html
Chris
1
Tetapi dalam kasus ini (demi tes ini) saya selalu memberikan parameter yang sama. Tidak ada aplikasi lain yang bisa menyelinap masuk dan memanggil kueri menggunakan params lain. Terima kasih atas artikelnya. Akan ditinjau.
Chad Decker
2
Ini bisa terjadi baik karena mengendus nilai parameter dan variabel atau karena melakukan penyederhanaan yang lebih besar. Contoh penyederhanaan yang lebih besar akan runtuh X = @X OR @X IS NULLke X=@Xdan melakukan pencarian Lihat di sini atau mendorong predikat lebih jauh terhadap tampilan dengan fungsi jendela
Martin Smith
3
Setelah Anda mengedit contoh Query Analyzer menggunakan variabel, bukan parameter. nilai dari mereka tidak pernah dihirup kecuali dengan RECOMPILE. Dalam setiap kejadian, tangkap rencana eksekusi dan lihat perbedaannya.
Martin Smith

Jawaban:

157

Ada kalanya menggunakan itu OPTION(RECOMPILE)masuk akal. Dalam pengalaman saya satu-satunya waktu ini adalah opsi yang layak adalah ketika Anda menggunakan SQL dinamis. Sebelum Anda menjelajahi apakah ini masuk akal dalam situasi Anda, saya akan merekomendasikan membangun kembali statistik Anda. Ini dapat dilakukan dengan menjalankan yang berikut ini:

EXEC sp_updatestats

Dan kemudian menciptakan kembali rencana eksekusi Anda. Ini akan memastikan bahwa ketika rencana eksekusi Anda dibuat, itu akan menggunakan informasi terbaru.

Menambahkan OPTION(RECOMPILE)membangun kembali paket eksekusi setiap kali query Anda dieksekusi. Saya belum pernah mendengar yang digambarkan sebagai creates a new lookup strategytetapi mungkin kita hanya menggunakan istilah yang berbeda untuk hal yang sama.

Ketika prosedur tersimpan dibuat (saya curiga Anda memanggil ad-hoc sql dari. NET tetapi jika Anda menggunakan kueri parameterisasi maka ini berakhir dengan panggilan proc tersimpan ) SQL Server berupaya menentukan rencana eksekusi paling efektif untuk kueri ini berdasarkan data dalam database Anda dan parameter yang dilewatkan ( parameter sniffing ), dan kemudian cache rencana ini. Ini berarti bahwa jika Anda membuat kueri di mana ada 10 catatan dalam basis data Anda dan kemudian jalankan ketika ada 100.000.000 catatan, rencana pelaksanaan yang di-cache mungkin tidak lagi paling efektif.

Singkatnya - saya tidak melihat alasan apa pun yang OPTION(RECOMPILE)akan bermanfaat di sini. Saya menduga Anda hanya perlu memperbarui statistik dan rencana eksekusi Anda. Membangun kembali statistik dapat menjadi bagian penting dari pekerjaan DBA tergantung pada situasi Anda. Jika Anda masih mengalami masalah setelah memperbarui statistik Anda, saya sarankan memposting kedua rencana eksekusi.

Dan untuk menjawab pertanyaan Anda - ya, saya akan mengatakan sangat tidak biasa untuk opsi terbaik Anda untuk mengkompilasi ulang rencana eksekusi setiap kali Anda mengeksekusi query.

Abe Miessler
sumber
22
Ya, sp_updatestats berhasil. Anda menekan paku di kepala ketika Anda menyebutkan kueri awalnya berjalan di atas meja dengan 10 catatan, dan sekarang tabel memiliki jutaan catatan. Itulah tepatnya kasus saya. Saya tidak menyebutkannya di pos karena saya pikir itu tidak penting. Hal-hal yang menarik. Terima kasih lagi.
Chad Decker
3
Ini satu-satunya cara yang saya temukan untuk bekerja dengan variabel tabel, karena SQL selalu berpikir bahwa ada satu baris tunggal. Ketika itu berisi beberapa ribu baris itu menjadi masalah.
Alex Zhukovskiy
4
Satu detail menarik: memperbarui statistik secara implisit membatalkan semua rencana cache yang menggunakan statistik ini, tetapi hanya jika statistik benar-benar berubah setelah tindakan pembaruan . Jadi untuk tabel readonly sangat miring, tampaknya eksplisit OPTION (RECOMPILE)mungkin satu-satunya solusi.
Groo
141

Seringkali ketika ada perbedaan drastis dari menjalankan ke menjalankan kueri saya menemukan bahwa itu sering salah satu dari 5 masalah.

  1. STATISTIK- Statistik kedaluwarsa. Database menyimpan statistik pada rentang dan distribusi jenis nilai dalam berbagai kolom pada tabel dan indeks. Ini membantu mesin kueri untuk mengembangkan "Rencana" serangan untuk bagaimana ia akan melakukan kueri, misalnya jenis metode yang akan digunakan untuk mencocokkan kunci antara tabel menggunakan hash atau melihat seluruh set. Anda dapat memanggil Perbarui Statistik di seluruh database atau hanya tabel atau indeks tertentu. Ini memperlambat kueri dari satu run ke run lain karena ketika statistik kedaluwarsa, kemungkinan rencana kueri tidak optimal untuk data yang baru dimasukkan atau diubah untuk kueri yang sama (dijelaskan lebih lanjut di bawah). Mungkin tidak tepat untuk Memperbarui Statistik segera pada basis data Produksi karena akan ada beberapa overhead, memperlambat dan lag tergantung pada jumlah data untuk sampel. Anda juga dapat memilih untuk menggunakan Pemindaian Lengkap atau Pengambilan Sampel untuk memperbarui Statistik. Jika Anda melihat Rencana Kueri, Anda kemudian dapat juga melihat statistik pada Indeks yang digunakan seperti menggunakan perintahDBCC SHOW_STATISTICS (nama tab, nama indeks) . Ini akan menunjukkan kepada Anda distribusi dan rentang kunci yang digunakan rencana kueri untuk mendasarkan pendekatannya.

  2. PARAMETER SNIFFING - Rencana kueri yang di-cache tidak optimal untuk parameter tertentu yang Anda lewati, meskipun kueri itu sendiri belum berubah. Misalnya, jika Anda memasukkan parameter yang hanya mengambil 10 dari 1.000.000 baris, maka paket kueri yang dibuat dapat menggunakan Hash Join, namun jika parameter yang Anda lewati akan menggunakan 750.000 dari 1.000.000 baris, paket yang dibuat mungkin merupakan pemindaian indeks atau pemindaian tabel. Dalam situasi seperti itu Anda bisa memberi tahu pernyataan SQL untuk menggunakan opsi OPTION (RECOMPILE) atau SP untuk menggunakan WITH RECOMPILE. Untuk memberi tahu Engine ini "Rencana Penggunaan Tunggal" dan tidak menggunakan Rencana Cached yang kemungkinan tidak berlaku. Tidak ada aturan tentang bagaimana membuat keputusan ini, itu tergantung pada mengetahui cara kueri akan digunakan oleh pengguna.

  3. INDEKS - Kemungkinan bahwa kueri belum berubah, tetapi perubahan di tempat lain seperti penghapusan indeks yang sangat berguna telah memperlambat kueri.

  4. ROWS CHANGED - Baris yang Anda tanyakan berubah secara drastis dari panggilan ke panggilan. Biasanya statistik diperbarui secara otomatis dalam kasus ini. Namun, jika Anda sedang membangun SQL dinamis atau memanggil SQL dalam loop ketat, ada kemungkinan Anda menggunakan Rencana Kueri yang kedaluwarsa berdasarkan jumlah baris atau statistik yang drastis. Lagi-lagi dalam hal ini OPTION (RECOMPILE) berguna.

  5. LOGIKnya Logika, kueri Anda tidak lagi efisien, tidak masalah untuk sejumlah kecil baris, tetapi tidak lagi skala. Ini biasanya melibatkan analisis mendalam dari Rencana Kueri. Misalnya, Anda tidak dapat lagi melakukan hal-hal dalam jumlah besar, tetapi harus melakukan Chunk dan melakukan Commit yang lebih kecil, atau Cross Product Anda baik-baik saja untuk set yang lebih kecil tetapi sekarang membutuhkan CPU dan Memori karena skala lebih besar, ini mungkin juga berlaku untuk menggunakan DISTINCT, Anda memanggil fungsi untuk setiap baris, pencocokan kunci Anda tidak menggunakan indeks karena konversi jenis CASTING atau NULLS atau fungsi ... Terlalu banyak kemungkinan di sini.

Secara umum ketika Anda menulis kueri, Anda harus memiliki beberapa gambaran mental tentang bagaimana data tertentu didistribusikan dalam tabel Anda. Kolom misalnya, dapat memiliki jumlah nilai yang berbeda yang terdistribusi secara merata, atau dapat miring, 80% dari waktu memiliki seperangkat nilai tertentu, apakah distribusi akan sering bervariasi dari waktu ke waktu atau cukup statis. Ini akan memberi Anda ide yang lebih baik tentang cara membangun kueri yang efisien. Tetapi juga ketika debugging kinerja permintaan memiliki dasar untuk membangun hipotesis mengapa itu lambat atau tidak efisien.

CodeCowboyOrg
sumber
2
Terima kasih teman. Ini informasi yang sangat bagus. Saya tidak akan bisa memahami jawaban Anda ketika saya awalnya memposting pertanyaan saya, tetapi sekarang masuk akal bagi saya.
Chad Decker
3
PARAMETER SNIFFING sejauh ini merupakan kutukan terbesar bagi keberadaan saya. Saya bahkan tidak tahu tentang perintah ini sampai pertanyaan wawancara gagal. Solusi saya untuk mengendus parameter selalu hash nilai parameter dan menambahkan "AND {hash} = {hash}" sehingga sql selalu berbeda untuk nilai yang berbeda. Peretasan, tapi berhasil.
Jeremy Boyd
27

Untuk menambah daftar yang sangat baik (diberikan oleh @CodeCowboyOrg) dari situasi di mana OPTION (RECOMPILE) dapat sangat membantu,

  1. Variabel tabel . Saat Anda menggunakan variabel tabel, tidak akan ada statistik pra-dibangun untuk variabel tabel, sering mengarah ke perbedaan besar antara estimasi dan baris aktual dalam rencana kueri. Menggunakan OPTION (RECOMPILE) pada kueri dengan variabel tabel memungkinkan pembuatan rencana kueri yang memiliki perkiraan jumlah baris yang lebih baik. Saya memiliki penggunaan kritis dari variabel tabel yang tidak dapat digunakan, dan yang akan saya tinggalkan, sampai saya menambahkan OPTION (RECOMPILE). Waktu berjalan berjalan dari jam menjadi hanya beberapa menit. Itu mungkin tidak biasa, tetapi dalam hal apapun, jika Anda menggunakan variabel tabel dan bekerja pada pengoptimalan, ada baiknya melihat apakah OPTION (RECOMPILE) membuat perbedaan.
DWright
sumber
1
Saya punya pertanyaan dengan 5 variabel tabel. Di komputer saya dijalankan selama lebih dari setengah jam. Di mesin rekan kerja saya dijalankan dalam <1 detik. Mesin memiliki perangkat keras yang serupa dan versi SQL Server yang sama. Jika kami berdua menambahkan OPTION (RECOMPILE) maka dijalankan dalam 2 detik pada kedua mesin. Dalam semua kasus, tes pelaksanaan dilakukan dalam SSMS. Apa yang bisa menyebabkan perbedaan ini?
Adam
1
Bisakah Anda membandingkan rencana eksekusi untuk itu di mesin Anda dan di mesin rekan Anda tanpa Opsi (kompilasi ulang)? Itu mungkin menunjukkan sumber perbedaan.
DWright
1
untuk tabel sementara, apakah situasinya sama?
Muflix
1
@muflix: pertanyaan bagus. Saya tidak percaya efeknya sama untuk tabel sementara, karena mereka memiliki statistik dan mesin harus membuat pilihan kompilasi ulang otomatis seperti untuk tabel lain, saya percaya (tapi saya tidak yakin). Mungkin orang lain tahu dengan kepastian yang lebih besar.
DWright
2
Statistik dalam tabel sementara tidak secara otomatis diperbarui atau dikompilasi ulang sehingga programmer perlu melakukan itu.
J. Michael Wuerth
1

Tindakan pertama sebelum menyetel kueri adalah defrag / membangun kembali indeks dan statistik, selain itu Anda membuang-buang waktu.

Anda harus memeriksa rencana pelaksanaan untuk melihat apakah itu stabil (sama ketika Anda mengubah parameter), jika tidak, Anda mungkin harus membuat indeks penutup (dalam hal ini untuk setiap tabel) (mengetahui bahwa sistem Anda dapat membuat yang juga berguna untuk pertanyaan lain).

sebagai contoh: buat index idx01_datafeed_trans Pada datafeed_trans (feedid, feedDate) TERMASUK (acctNo, tradeDate)

jika paket itu stabil atau Anda bisa menstabilkannya, Anda bisa mengeksekusi kalimat dengan sp_executesql ('sql kalimat') untuk menyimpan dan menggunakan rencana eksekusi yang sudah diperbaiki.

jika paket tidak stabil Anda harus menggunakan pernyataan ad-hoc atau EXEC ('sql kalimat') untuk mengevaluasi dan membuat rencana eksekusi setiap kali. (atau prosedur tersimpan "dengan kompilasi ulang").

Semoga ini bisa membantu.

Cristian Solervicéns
sumber
1

Necroing pertanyaan ini tetapi ada penjelasan yang tampaknya tidak ada yang mempertimbangkan.

STATISTIK - Statistik tidak tersedia atau menyesatkan

Jika semua hal berikut ini benar:

  1. Feedid kolom dan feedDate cenderung sangat berkorelasi (misalnya id feed lebih spesifik dari tanggal feed dan parameter tanggal adalah informasi yang berlebihan).
  2. Tidak ada indeks dengan kedua kolom sebagai kolom berurutan.
  3. Tidak ada statistik yang dibuat secara manual yang mencakup kedua kolom ini.

Kemudian sql server mungkin salah berasumsi bahwa kolom tidak berkorelasi, yang mengarah ke perkiraan kardinalitas yang lebih rendah dari yang diharapkan untuk menerapkan kedua pembatasan dan rencana eksekusi yang buruk dipilih. Perbaikan dalam hal ini adalah membuat objek statistik yang menghubungkan dua kolom, yang bukan operasi yang mahal.

MonkeyPushButton
sumber