Bagaimana mengukur atau menemukan biaya pembuatan rencana kueri?

18

Saya memiliki kasus khusus di mana parameter sniffing menyebabkan rencana eksekusi "buruk" mendarat di cache paket, menyebabkan eksekusi selanjutnya dari prosedur tersimpan saya menjadi sangat lambat. Saya dapat "memecahkan" masalah ini dengan variabel lokal OPTIMIZE FOR ... UNKNOWN,, dan OPTION(RECOMPILE). Namun, saya juga bisa menyelami kueri dan mencoba mengoptimalkannya.

Saya mencoba menentukan apakah saya harus : diberi waktu terbatas untuk memperbaiki masalah, saya ingin tahu biaya tidak melakukannya. Seperti yang saya lihat, jika saya tetap bertahan OPTION(RECOMPILE), efek bersihnya adalah rencana kueri dibuat kembali setiap kali kueri dijalankan. Jadi, saya pikir saya perlu tahu:

Bagaimana cara mengetahui biaya pembuatan rencana kueri?

Untuk menjawab pertanyaan saya sendiri, saya telah mencari di Google (mis. Dengan pertanyaan ini ), dan saya telah membaca dokumentasi kolom untuk dm_exec_query_statsDMV . Saya juga telah memeriksa jendela output dalam SSMS untuk "Rencana Kueri Aktual" untuk menemukan info ini. Akhirnya, saya sudah mencari DBA.SE . Tak satu pun dari mereka mengarah pada jawaban.

Adakah yang bisa memberitahuku? Apakah mungkin menemukan atau mengukur waktu yang diperlukan untuk pembuatan rencana?

Jeroen
sumber
5
Saya akan merekomendasikan mengambil salinan Di dalam SQL Server Query Optimizer oleh Benjamin Nevarez . Gratis. Bab 5 'Proses Optimasi' dapat membantu Anda menentukan waktu kompilasi untuk kueri Anda. Paling tidak, ini informatif tentang apa yang dilakukan optimizer untuk membuat rencana kueri.
Mark Sinkinson

Jawaban:

18

Bagaimana cara mengetahui biaya pembuatan rencana kueri?

Anda bisa melihat properti dari simpul akar dalam rencana kueri, misalnya:

Ekstrak sifat akar
(tangkapan layar dari Sentry One Plan Explorer gratis )

Informasi ini juga tersedia dengan menanyakan cache rencana, misalnya menggunakan permintaan berdasarkan hubungan berikut:

WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    CompileTime = c.value('(QueryPlan/@CompileTime)[1]', 'int'),
    CompileCPU = c.value('(QueryPlan/@CompileCPU)[1]', 'int'),
    CompileMemory = c.value('(QueryPlan/@CompileMemory)[1]', 'int'),
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_query_plan(CP.plan_handle) AS QP
CROSS APPLY sys.dm_exec_sql_text(CP.plan_handle) AS ST
CROSS APPLY QP.query_plan.nodes('ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple') AS N(c);

Hasil fragmen

Untuk perawatan lengkap dari opsi yang Anda miliki untuk menangani pertanyaan semacam ini, lihat artikel Erland Sommarskog yang baru - baru ini diperbarui .

Paul White mengatakan GoFundMonica
sumber
4

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.

  1. 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).

  2. 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.

  3. 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 = 1kueri berbeda, dan karenanya, rencana kueri berbeda, daripada memiliki WHERE StatusID = 2.

  4. 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.

  5. 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;
Solomon Rutzky
sumber
Terima kasih telah meluangkan waktu (cukup banyak) untuk menjawab! Saya agak skeptis tentang bit pertama tentang cara mendapatkan waktu kompilasi, mengingat itu adalah faktor 3 lebih rendah dari hasil yang saya dapatkan dengan menggunakan pendekatan @ PaulWhite . - Kedua pada Dynamic SQL bit yang menarik (meskipun juga akan membutuhkan waktu untuk menerapkan, setidaknya lebih dari hanya menampar sebuah OPTIONatas permintaan saya), dan tidak akan menyakiti saya terlalu banyak karena sproc ini juga dimanfaatkan dalam tes integrasi. - Dalam hal apapun: terima kasih atas wawasan Anda!
Jeroen