Apa arti dan manfaat menggunakan SqlCommand.Prepare ()?

14

Saya menemukan kode pengembang di mana metode SqlCommand.Prepare () (lihat MSDN) secara luas digunakan sebelum pelaksanaan query SQL. Dan saya bertanya-tanya apa manfaat dari ini?

Sampel:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

Saya telah bermain-main sedikit dan ditelusuri. Eksekusi Perintah setelah memanggil Prepare()metode membuat Sql Server menjalankan pernyataan berikut:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

Setelah itu ketika Parameter mendapatkan nilainya dan SqlCommand.ExecuteNonQuery()dipanggil, berikut ini dieksekusi pada Sql-Server:

exec sp_execute 1,@id=20

Bagi saya ini sepertinya pernyataan yang dikompilasi segera Prepare()dieksekusi. Saya bertanya-tanya apa manfaat dari ini? Apakah ini berarti bahwa itu dimasukkan ke dalam cache rencana dan dapat digunakan kembali segera setelah permintaan akhir dieksekusi dengan nilai parameter yang diinginkan?

Saya menemukan (dan mendokumentasikannya dalam pertanyaan lain ) bahwa SqlCommands yang dieksekusi dengan SqlParameters selalu dibungkus dengan sp_executesqlpanggilan prosedur. Ini membuat Sql Server dapat menyimpan dan menggunakan kembali paket independen dari nilai parameter.

Mengenai hal ini saya bertanya-tanya apakah prepare()metode ini tidak berguna atau usang atau jika saya kehilangan sesuatu di sini?

Magier
sumber
Saya selalu berpikir itu ada hubungannya dengan mencegah SQL Injection, jadi Anda akan menyiapkan kueri untuk menerima variabel jenis tertentu. Dengan begitu jika Anda tidak meneruskan variabel jenis itu, kueri gagal
Mark Sinkinson
Meskipun dengan mengatakan itu, penjelasan PHP ini mungkin sama validnya
Mark Sinkinson
"Ya, Tuan. Pengemudi, bersiaplah untuk pindah." "Apa yang kamu persiapkan ?! Kamu selalu bersiap! Pergi saja!"
Jon of All Trades

Jawaban:

19

Mempersiapkan batch SQL secara terpisah dari mengeksekusi batch SQL yang disiapkan adalah konstruksi yang efektif **tidak berguna untuk SQL Server mengingat bagaimana rencana eksekusi di-cache. Memisahkan langkah-langkah persiapan (parsing, mengikat parameter apa pun, dan kompilasi) dan eksekusi hanya masuk akal ketika tidak ada caching. Tujuannya adalah untuk menghemat waktu yang dihabiskan untuk parsing dan kompilasi dengan menggunakan kembali rencana yang ada, dan inilah yang dilakukan cache rencana internal. Tetapi tidak semua RDBMS melakukan level caching ini (jelas dari keberadaan fungsi-fungsi ini) sehingga diserahkan kepada kode klien untuk meminta agar suatu rencana "di-cache", dan kemudian simpan cache ID itu, kemudian gunakan kembali Itu. Ini adalah beban tambahan pada kode klien (dan programmer untuk mengingat untuk melakukannya dan melakukannya dengan benar) yang tidak perlu. Bahkan, halaman MSDN untuk metode IDbCommand.Prepare () menyatakan:

Server secara otomatis cache rencana untuk digunakan kembali sebagaimana diperlukan; oleh karena itu, tidak perlu memanggil metode ini secara langsung di aplikasi klien Anda.

Perlu dicatat bahwa, sejauh pengujian saya menunjukkan (yang cocok dengan apa yang Anda tunjukkan dalam Pertanyaan), memanggil SqlCommand.Prepare () , tidak melakukan "persiapan" - hanya operasi: ia memanggil sp_prepexec yang menyiapkan dan mengeksekusi SQL ; itu tidak memanggil sp_prepare yang hanya parse dan kompilasi (dan tidak mengambil nilai parameter apa pun, hanya nama dan tipe data mereka). Karenanya, tidak mungkin ada manfaat "pra-kompilasi" dari panggilan SqlCommand.Preparekarena melakukan eksekusi langsung (dengan asumsi tujuannya adalah untuk menunda eksekusi). NAMUN , ada manfaat kecil potensial yang menarik dari menelepon SqlCommand.Prepare(), bahkan jika itu memanggil sp_prepexecbukan sp_prepare. Silakan lihat catatannya (** ) di bagian bawah untuk detail.

Pengujian saya menunjukkan bahwa bahkan ketika SqlCommand.Prepare()dipanggil (dan harap dicatat bahwa panggilan ini tidak mengeluarkan perintah apa pun pada SQL Server), ExecuteNonQueryatau ExecuteReaderyang berikut sepenuhnya dieksekusi, dan jika itu ExecuteReader, ia mengembalikan baris. Namun, SQL Server Profiler menunjukkan bahwa SqlCommand.Execute______()panggilan pertama setelah SqlCommand.Prepare()panggilan terdaftar sebagai acara "Siapkan SQL", dan .Execute___()panggilan berikutnya mendaftar sebagai peristiwa "Exec Prepared SQL".


Kode Uji

Sudah diunggah ke PasteBin di: http://pastebin.com/Yc2Tfvup . Kode ini menciptakan .NET / C # Aplikasi Konsol yang dimaksudkan untuk berjalan saat pelacakan SQL Server Profiler sedang berjalan (atau sesi Acara yang Diperpanjang). Itu berhenti setelah setiap langkah sehingga akan menjadi jelas pernyataan mana yang memiliki efek tertentu.


MEMPERBARUI

Menemukan lebih banyak info, dan sedikit alasan potensial untuk menghindari panggilan SqlCommand.Prepare(). Satu hal yang saya perhatikan dalam pengujian saya, dan apa yang harus dicatat bagi siapa saja yang menjalankan aplikasi Konsol pengujian itu: tidak pernah ada panggilan eksplisit yang dibuat sp_unprepare. Saya melakukan penggalian dan menemukan posting berikut di forum MSDN:

SqlCommand - Mempersiapkan atau tidak mempersiapkan?

Jawaban yang diterima berisi banyak info, tetapi poin penting adalah:

  • ** Apa yang dipersiapkan sebenarnya menyelamatkan Anda terutama waktu yang diperlukan untuk mengirimkan string kueri melalui kawat.
  • Kueri yang disiapkan tidak memiliki jaminan bahwa program cache disimpan, dan tidak lebih cepat dari permintaan ad-hoc setelah mencapai server.
  • RPC (CommandType.StoredProcedure) tidak mendapatkan apa-apa dari persiapan.
  • Di server, pegangan yang disiapkan pada dasarnya adalah indeks ke dalam peta di dalam memori yang berisi TSQL untuk dieksekusi.
  • Peta disimpan pada basis per koneksi, tidak ada penggunaan cross-connection yang memungkinkan.
  • Reset dikirim ketika klien menggunakan kembali koneksi dari pool membersihkan peta.

Saya juga menemukan posting berikut dari tim SQLCAT, yang tampaknya terkait, tetapi hanya bisa menjadi masalah khusus untuk ODBC sedangkan SqlClient tidak membersihkan dengan benar setelah membuang SqlConnection. Sulit untuk mengatakan karena posting SQLCAT tidak menyebutkan tes tambahan yang akan membantu membuktikan ini sebagai penyebab, seperti membersihkan kumpulan koneksi, dll.

Perhatikan pernyataan SQL yang disiapkan itu


KESIMPULAN

Mengingat bahwa:

  • satu-satunya manfaat nyata dari panggilan SqlCommand.Prepare()tampaknya adalah Anda tidak perlu mengirim teks permintaan lagi melalui jaringan,
  • memanggil sp_preparedan sp_prepexecmenyimpan teks kueri sebagai bagian dari memori koneksi (yaitu memori SQL Server)

Saya akan merekomendasikan untuk tidak menelepon SqlCommand.Prepare()karena satu-satunya manfaat potensial adalah menghemat paket jaringan sementara kekurangannya adalah mengambil lebih banyak memori server. Sementara jumlah memori yang dikonsumsi mungkin sangat kecil di sebagian besar kasus, bandwidth jaringan jarang menjadi masalah karena sebagian besar server DB terhubung langsung ke server aplikasi lebih dari 10 Megabit minimum (lebih mungkin 100 Megabit atau Gigabit hari ini) koneksi ( dan beberapa bahkan ada di kotak yang sama ;-). Itu dan memori hampir selalu merupakan sumber daya yang lebih langka daripada bandwidth jaringan.

Saya kira, bagi siapa pun yang menulis perangkat lunak yang dapat terhubung ke banyak basis data, maka kenyamanan antarmuka standar dapat mengubah analisis biaya / manfaat ini. Tetapi bahkan dalam kasus itu, saya harus percaya bahwa itu masih cukup mudah untuk abstrak antarmuka DB "standar" yang memungkinkan untuk penyedia yang berbeda (dan karenanya perbedaan di antara mereka, seperti tidak perlu menelepon Prepare()) mengingat bahwa melakukannya adalah objek dasar Pemrograman berorientasi yang kemungkinan besar sudah Anda lakukan dalam kode aplikasi Anda ;-).

Solomon Rutzky
sumber
Terima kasih atas jawaban yang luas ini. Saya menguji langkah Anda dan memiliki dua penyimpangan dari harapan / hasil Anda: pada langkah 4 saya mendapat hasil tambahan. Pada langkah 10 saya mendapat plan_handle lain daripada pada langkah 2.
Magier
1
@ Majer Tidak yakin tentang langkah 4 ... mungkin Anda memiliki opsi SET yang berbeda atau entah bagaimana teks kueri di antara keduanya berbeda? Bahkan perbedaan satu karakter (terlihat atau tidak) akan menjadi rencana yang berbeda. Salin dan tempel dari halaman web ini mungkin tidak membantu, jadi salin kueri (segala sesuatu di antara tanda kutip tunggal) dari sp_preparedalam panggilan ke sp_executesql, hanya untuk memastikan. Untuk langkah 10, ya, saya melakukan lebih banyak pengujian kemarin dan memang melihat beberapa kesempatan dengan pegangan berbeda untuk sp_prepare, tetapi masih belum pernah sama untuk sp_executesql. Saya akan menguji satu hal lagi dan memperbarui jawaban saya.
Solomon Rutzky
1
@ Majer Sebenarnya, tes yang saya jalankan sebelum menutupinya dan saya tidak berpikir ada benar menggunakan kembali rencana, terutama mengingat bahwa posting forum MSDN mengatakan bahwa itu hanya cache SQL. Saya masih penasaran bagaimana bisa menggunakan kembali plan_handle sedangkan sp_executesqltidak bisa atau tidak. Tapi bagaimanapun, waktu parse masih ada di sana sp_preparejika rencana tersebut telah dihapus dari cache jadi saya akan menghapus bagian dari jawaban saya (menarik tetapi tidak relevan). Bagian itu lebih merupakan catatan tambahan yang menarik karena tidak membahas pertanyaan langsung Anda. Semuanya masih berlaku.
Solomon Rutzky
1
Ini adalah jenis penjelasan yang saya cari.
CSharpie
5

Mengenai hal ini saya bertanya-tanya apakah metode persiapan () agak tidak berguna atau usang atau jika saya kehilangan sesuatu di sini?

Saya akan mengatakan bahwa metode Siapkan memiliki nilai SqlCommand dunia terbatas, bukan berarti itu sama sekali tidak berguna. Satu manfaat adalah bahwa Mempersiapkan adalah bagian dari antarmuka IDbCommand yang mengimplementasikan SqlCommand. Ini memungkinkan kode yang sama untuk dijalankan terhadap penyedia DBMS lainnya (yang mungkin memerlukan Siapkan) tanpa logika kondisional untuk menentukan apakah Siapkan harus dipanggil atau tidak.

Mempersiapkan tidak memiliki nilai dengan .NET Provider untuk SQL Server selain kasus penggunaan ini, AFAIK.

Dan Guzman
sumber