Ini adalah salah satu perdebatan agama/politik yang telah berkecamuk selama bertahun-tahun:haruskah saya menggunakan prosedur tersimpan, atau haruskah saya memasukkan kueri ad hoc dalam aplikasi saya? Saya selalu menjadi pendukung prosedur tersimpan, karena beberapa alasan:
- Saya tidak dapat menerapkan perlindungan injeksi SQL jika kueri dibuat dalam kode aplikasi. Pengembang mungkin mengetahui kueri berparameter, tetapi tidak ada yang memaksa mereka untuk menggunakannya dengan benar.
- Saya tidak dapat menyesuaikan kueri yang disematkan dalam kode sumber aplikasi, juga tidak dapat menerapkan praktik terbaik apa pun.
- Jika saya menemukan peluang untuk penyetelan kueri, untuk menerapkannya, saya harus mengkompilasi ulang dan menerapkan ulang kode aplikasi, bukan hanya mengubah prosedur tersimpan.
- Jika kueri digunakan di banyak tempat dalam aplikasi, atau di beberapa aplikasi, dan memerlukan perubahan, saya harus mengubahnya di banyak tempat, sedangkan dengan prosedur tersimpan saya hanya perlu mengubahnya sekali (masalah penerapan samping).
Saya juga melihat bahwa banyak orang membuang prosedur tersimpan demi ORM. Untuk aplikasi sederhana ini mungkin akan baik-baik saja, tetapi karena aplikasi Anda menjadi lebih kompleks, Anda mungkin menemukan bahwa ORM pilihan Anda tidak mampu melakukan pola kueri tertentu, *memaksa* Anda untuk menggunakan prosedur tersimpan. Jika mendukung prosedur tersimpan, yaitu.
Meskipun saya masih menemukan semua argumen ini cukup menarik, bukan itu yang ingin saya bicarakan hari ini; Saya ingin berbicara tentang kinerja.
Banyak argumen di luar sana hanya akan mengatakan, "prosedur tersimpan berkinerja lebih baik!" Itu mungkin sedikit benar di beberapa titik, tetapi karena SQL Server menambahkan kemampuan untuk mengkompilasi pada tingkat pernyataan daripada tingkat objek, dan telah memperoleh fungsionalitas yang kuat seperti optimize for ad hoc workloads
, ini bukan lagi argumen yang sangat kuat. Penyetelan indeks dan pola kueri yang masuk akal memiliki dampak yang jauh lebih besar pada kinerja daripada memilih untuk menggunakan prosedur tersimpan; pada versi modern, saya ragu Anda akan menemukan banyak kasus di mana kueri yang sama persis menunjukkan perbedaan kinerja yang nyata, kecuali jika Anda juga memperkenalkan variabel lain (seperti menjalankan prosedur secara lokal vs. aplikasi di pusat data yang berbeda di benua yang berbeda).
Meskipun demikian, ada aspek kinerja yang sering diabaikan saat menangani kueri ad hoc:cache paket. Kita dapat menggunakan optimize for ad hoc workloads
untuk mencegah paket sekali pakai mengisi cache kami (Kimberly Tripp (@KimberlyLTripp) dari SQLskills.com memiliki beberapa informasi hebat tentang ini di sini), dan itu memengaruhi paket sekali pakai terlepas dari apakah kueri dijalankan dari dalam prosedur tersimpan atau dijalankan secara ad hoc. Dampak berbeda yang mungkin tidak Anda sadari, terlepas dari setelan ini, adalah saat identik paket menggunakan banyak slot di cache karena perbedaan SET
pilihan atau delta kecil dalam teks kueri yang sebenarnya. Seluruh fenomena "lambat dalam aplikasi, cepat dalam SSMS" telah membantu banyak orang menyelesaikan masalah yang melibatkan pengaturan seperti SET ARITHABORT
. Hari ini saya ingin berbicara tentang perbedaan teks kueri dan mendemonstrasikan sesuatu yang mengejutkan orang setiap kali saya membahasnya.
Tembolok untuk dibakar
Katakanlah kita memiliki sistem yang sangat sederhana yang menjalankan AdventureWorks2012. Dan hanya untuk membuktikan bahwa itu tidak membantu, kami telah mengaktifkan optimize for ad hoc workloads
:
EXEC sp_configure 'tampilkan opsi lanjutan', 1;GORECONFIGURE WITH OVERRIDE;GOEXEC sp_configure 'optimalkan untuk beban kerja ad hoc', 1;GORECONFIGURE WITH OVERRIDE;
Dan kemudian kosongkan cache paket:
DBCC FREEPROCCACHE;
Sekarang kami membuat beberapa variasi sederhana untuk kueri yang identik. Variasi ini berpotensi mewakili gaya pengkodean untuk dua pengembang yang berbeda – sedikit perbedaan dalam spasi, huruf besar/kecil, dll.
SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID>=75120ORDER BY OrderDate DESC;GO -- ubah>=75120 menjadi> 75119 (logika yang sama karena merupakan INT)GO SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID> 75119ORDER BY OrderDate DESC;GO -- ubah kueri ke semua huruf kecilGO pilih atas (1) salesorderid, orderdate, subtotaldari sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- hilangkan tanda kurung di sekitar argumen untuk topGO pilih top 1 salesorderid, orderdate, subtotaldari sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- tambahkan spasi setelah top 1GO pilih top 1 salesorderid, orderdate, subtotaldari penjualan.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- hilangkan spasi di antara komaGO pilih top 1 salesorderid,orderdate,subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GOJika kita menjalankan batch itu sekali, dan kemudian memeriksa cache rencana, kita melihat bahwa kita memiliki 6 salinan, pada dasarnya, rencana eksekusi yang sama persis. Ini karena teks kueri adalah hash biner, artinya huruf besar/kecil dan spasi membuat perbedaan dan dapat membuat kueri yang identik terlihat unik untuk SQL Server.
PILIH [teks], size_in_bytes, usecounts, cacheobjtypeFROM sys.dm_exec_cached_plans AS pCROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS tWHERE LOWER(t.[text]) LIKE '%ales.sales%'+'orderheaders';'Hasil:
teks size_in_bytes penggunaan cacheobjtype pilih 1 salesorderid teratas,o… 272 1 Stub Rencana Terkompilasi pilih 1 salesorderid teratas, … 272 1 Stub Rencana Terkompilasi pilih 1 salesorderid teratas, o… 272 1 Stub Rencana Terkompilasi pilih atas (1) salesorderid,… 272 1 Stub Rencana Terkompilasi PILIH TOP (1) SalesOrderID,… 272 1 Stub Rencana Terkompilasi PILIH TOP (1) SalesOrderID,… 272 1 Stub Rencana Terkompilasi Hasil setelah eksekusi pertama kueri "identik"
Jadi, ini tidak sepenuhnya sia-sia, karena pengaturan ad hoc memungkinkan SQL Server hanya menyimpan rintisan kecil pada eksekusi pertama. Jika kita menjalankan batch lagi (tanpa mengosongkan cache prosedur), kita melihat hasil yang sedikit lebih mengkhawatirkan:
teks size_in_bytes penggunaan cacheobjtype pilih 1 salesorderid teratas,o… 49.152 1 Rencana yang Dikompilasi pilih 1 salesorderid teratas, … 49.152 1 Rencana yang Dikompilasi pilih 1 salesorderid teratas, o… 49.152 1 Rencana yang Dikompilasi pilih atas (1) salesorderid,… 49.152 1 Rencana yang Dikompilasi PILIH TOP (1) SalesOrderID,… 49.152 1 Rencana yang Dikompilasi PILIH TOP (1) SalesOrderID,… 49.152 1 Rencana yang Dikompilasi Hasil setelah eksekusi kedua kueri "identik"
Hal yang sama terjadi untuk kueri berparameter, terlepas dari apakah parameterisasi sederhana atau dipaksakan. Dan hal yang sama terjadi ketika pengaturan ad hoc tidak diaktifkan, kecuali itu terjadi lebih cepat.
Hasil akhirnya adalah bahwa ini dapat menghasilkan banyak rencana cache bloat, bahkan untuk kueri yang terlihat identik – hingga ke dua kueri di mana satu pengembang membuat indentasi dengan tab dan indentasi lainnya dengan 4 spasi. Saya tidak perlu memberi tahu Anda bahwa mencoba menerapkan jenis konsistensi ini di seluruh tim bisa dari mana saja dari membosankan hingga tidak mungkin. Jadi menurut saya ini memberikan anggukan kuat untuk memodulasi, menyerah pada KERING, dan memusatkan jenis kueri ini ke dalam satu prosedur tersimpan.
Peringatan
Tentu saja, jika Anda menempatkan kueri ini dalam prosedur tersimpan, Anda hanya akan memiliki satu salinannya, sehingga Anda sepenuhnya menghindari potensi memiliki beberapa versi kueri dengan teks kueri yang sedikit berbeda. Sekarang, Anda juga dapat berargumen bahwa pengguna yang berbeda mungkin membuat prosedur tersimpan yang sama dengan nama berbeda, dan di setiap prosedur tersimpan ada sedikit variasi teks kueri. Meskipun memungkinkan, saya pikir itu mewakili masalah yang sama sekali berbeda. :-)