Dengan asumsi "biaya" adalah dalam hal waktu (meskipun tidak yakin apa lagi yang bisa dalam hal ;-), maka paling tidak Anda harus bisa merasakannya dengan melakukan sesuatu seperti berikut:
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases'; -- replace with your proc
SET STATISTICS TIME OFF;
Item pertama yang dilaporkan di tab "Pesan" adalah:
SQL Server menguraikan dan mengkompilasi waktu:
Saya akan menjalankan ini setidaknya 10 kali dan rata-rata milidetik "CPU" dan "Berlalu".
Idealnya Anda akan menjalankan ini di Produksi sehingga Anda bisa mendapatkan perkiraan waktu yang sebenarnya, tetapi jarang ada orang yang diizinkan untuk menghapus cache rencana di Produksi. Untungnya, mulai di SQL Server 2008 menjadi mungkin untuk menghapus paket tertentu dari cache. Dalam hal ini Anda dapat melakukan hal berikut:
DECLARE @SQL NVARCHAR(MAX) = '';
;WITH cte AS
(
SELECT DISTINCT stat.plan_handle
FROM sys.dm_exec_query_stats stat
CROSS APPLY sys.dm_exec_text_query_plan(stat.plan_handle, 0, -1) qplan
WHERE qplan.query_plan LIKE N'%sp[_]help%' -- replace "sp[_]help" with proc name
)
SELECT @SQL += N'DBCC FREEPROCCACHE ('
+ CONVERT(NVARCHAR(130), cte.plan_handle, 1)
+ N');'
+ NCHAR(13) + NCHAR(10)
FROM cte;
PRINT @SQL;
EXEC (@SQL);
SET STATISTICS TIME ON;
EXEC sp_help 'sys.databases' -- replace with your proc
SET STATISTICS TIME OFF;
Namun, tergantung pada variabilitas dari nilai-nilai yang diteruskan untuk parameter (s) menyebabkan rencana cache "buruk", ada metode lain untuk mempertimbangkan bahwa itu adalah jalan tengah antara OPTION(RECOMPILE)
dan OPTION(OPTIMIZE FOR UNKNOWN)
: Dynamic SQL. Ya, saya mengatakannya. Dan yang saya maksud adalah Dynamic SQL non-parameter. Inilah sebabnya.
Anda jelas memiliki data yang memiliki distribusi tidak merata, setidaknya dalam hal satu atau lebih nilai parameter input. Kelemahan dari opsi yang disebutkan adalah:
OPTION(RECOMPILE)
akan menghasilkan rencana untuk setiap eksekusi dan Anda tidak akan pernah dapat mengambil manfaat dari penggunaan kembali paket apa pun , bahkan jika nilai parameter yang diteruskan lagi identik dengan proses sebelumnya. Untuk procs yang sering dipanggil - sekali setiap beberapa detik atau lebih sering - ini akan menyelamatkan Anda dari situasi yang mengerikan sesekali, tetapi masih membuat Anda dalam situasi yang selalu tidak terlalu hebat.
OPTION(OPTIMIZE FOR (@Param = value))
akan menghasilkan rencana berdasarkan nilai tertentu, yang dapat membantu beberapa kasus tetapi tetap membuat Anda terbuka untuk masalah saat ini.
OPTION(OPTIMIZE FOR UNKNOWN)
akan menghasilkan rencana berdasarkan pada jumlah apa untuk distribusi rata-rata, yang akan membantu beberapa pertanyaan tetapi merugikan yang lain. Ini harus sama dengan opsi menggunakan variabel lokal.
Dynamic SQL, bagaimanapun, ketika dilakukan dengan benar , akan memungkinkan berbagai nilai yang diteruskan untuk memiliki rencana kueri terpisah mereka sendiri yang ideal (well, sebanyak yang akan terjadi). Biaya utama di sini adalah bahwa ketika variasi nilai yang diteruskan meningkat, jumlah rencana eksekusi dalam cache meningkat, dan mereka mengambil memori. Biaya kecil adalah:
perlu memvalidasi parameter string untuk mencegah SQL Suntikan
mungkin perlu mengatur Sertifikat dan Pengguna berbasis Sertifikat untuk mempertahankan abstraksi keamanan yang ideal karena Dynamic SQL memerlukan izin tabel langsung.
Jadi, inilah cara saya mengelola situasi ini ketika saya memiliki procs yang dipanggil lebih dari sekali per detik dan mencapai beberapa tabel, masing-masing dengan jutaan baris. Saya telah mencoba OPTION(RECOMPILE)
tetapi ini terbukti terlalu merusak proses dalam 99% kasus yang tidak memiliki parameter sniffing / masalah rencana cache yang buruk. Dan harap diingat bahwa salah satu dari procs ini memiliki sekitar 15 pertanyaan di dalamnya dan hanya 3 - 5 dari mereka yang dikonversi ke Dynamic SQL seperti dijelaskan di sini; SQL dinamis tidak digunakan kecuali jika diperlukan untuk permintaan tertentu.
Jika ada beberapa parameter input untuk prosedur tersimpan, cari tahu mana yang digunakan dengan kolom yang memiliki distribusi data sangat berbeda (dan karenanya menyebabkan masalah ini) dan mana yang digunakan dengan kolom yang memiliki distribusi lebih merata (dan tidak boleh menyebabkan masalah ini).
Membangun string SQL dinamis menggunakan parameter untuk par input input proc yang terkait dengan kolom yang terdistribusi secara merata. Parameterisasi ini membantu mengurangi peningkatan yang dihasilkan dalam rencana pelaksanaan di cache yang terkait dengan permintaan ini.
Untuk parameter lainnya yang terkait dengan distribusi yang sangat bervariasi, parameter tersebut harus digabungkan ke dalam SQL dinamis sebagai nilai literal. Karena kueri unik ditentukan oleh perubahan apa pun pada teks kueri, memiliki WHERE StatusID = 1
kueri berbeda, dan karenanya, rencana kueri berbeda, daripada memiliki WHERE StatusID = 2
.
Jika salah satu parameter input proc yang akan digabungkan ke dalam teks kueri adalah string, maka mereka harus divalidasi untuk melindungi terhadap SQL Injection (meskipun ini lebih kecil kemungkinannya terjadi jika string yang diteruskan dihasilkan oleh aplikasi dan bukan pengguna, tapi tetap saja). Setidaknya lakukan REPLACE(@Param, '''', '''''')
untuk memastikan bahwa tanda kutip tunggal menjadi lolos tanda kutip tunggal.
Jika perlu, buat Sertifikat yang akan digunakan untuk membuat Pengguna dan menandatangani prosedur tersimpan sehingga izin meja langsung akan diberikan hanya kepada Pengguna berbasis Sertifikat baru dan tidak untuk [public]
atau kepada Pengguna yang seharusnya tidak memiliki izin seperti itu. .
Contoh proc:
CREATE PROCEDURE MySchema.MyProc
(
@Param1 INT,
@Param2 DATETIME,
@Param3 NVARCHAR(50)
)
AS
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'
SELECT tab.Field1, tab.Field2, ...
FROM MySchema.SomeTable tab
WHERE tab.Field3 = @P1
AND tab.Field8 >= CONVERT(DATETIME, ''' +
CONVERT(NVARCHAR(50), @Param2, 121) +
N''')
AND tab.Field2 LIKE N''' +
REPLACE(@Param3, N'''', N'''''') +
N'%'';';
EXEC sp_executesql
@SQL,
N'@P1 INT',
@P1 = @Param1;