Parameter Sniffing vs VARIABLES vs Recompile vs OPTIMIZE FOR UNKNOWN

40

Jadi kami memiliki proc yang berjalan lama menyebabkan masalah pagi ini (30 detik + waktu berjalan). Kami memutuskan untuk memeriksa apakah parameter sniffing yang harus disalahkan. Jadi, kami menulis ulang proc dan mengatur parameter yang masuk ke variabel untuk mengalahkan parameter sniffing. Pendekatan yang dicoba / benar. Bam, waktu kueri membaik (kurang dari 1 detik). Ketika melihat rencana permintaan perbaikan ditemukan dalam indeks yang tidak digunakan asli.

Hanya untuk memverifikasi bahwa kami tidak mendapatkan false positive, kami melakukan freeproccache dbcc pada proc asli dan reran untuk melihat apakah hasil yang ditingkatkan akan sama. Tapi, yang mengejutkan kami, proc asli masih berjalan lambat. Kami mencoba lagi dengan DENGAN RECOMPILE, masih lambat (kami mencoba kompilasi ulang pada panggilan ke proc dan di dalam proc itu sendiri). Kami bahkan me-restart server (kotak dev jelas).

Jadi, pertanyaan saya adalah ini ... bagaimana bisa mengendus parameter menjadi menyalahkan ketika kita mendapatkan permintaan lambat yang sama pada cache rencana kosong ... seharusnya tidak ada parameter untuk snif ???

Apakah kita sebaliknya dipengaruhi oleh statistik tabel yang tidak terkait dengan cache paket. Dan jika demikian, mengapa pengaturan parameter yang masuk ke variabel membantu ??

Dalam pengujian lebih lanjut kami juga menemukan bahwa memasukkan OPSI (MENGOPTIMALKAN UNTUK TIDAK DIKETAHUI) pada bagian dalam proc DID mendapatkan rencana perbaikan yang diharapkan.

Jadi, beberapa dari kalian lebih pintar dari saya, dapatkah Anda memberikan beberapa petunjuk tentang apa yang terjadi di balik layar untuk menghasilkan jenis hasil seperti ini?

Pada catatan lain, rencana lambat juga dibatalkan awal dengan alasan GoodEnoughPlanFoundsementara rencana cepat tidak memiliki alasan membatalkan awal dalam rencana aktual.

Singkatnya

  • Membuat variabel di luar parameter yang masuk (1 detik)
  • dengan kompilasi ulang (30+ dtk)
  • dbcc freeproccache (30+ dtk)
  • OPTION (OPTIMIZE FOR UKNOWN) (1 detik)

MEMPERBARUI:

Lihat paket eksekusi lambat di sini: https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

Lihat paket eksekusi cepat di sini: https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml

Catatan: tabel, skema, nama objek berubah karena alasan keamanan.

RThomas
sumber

Jawaban:

43

Pertanyaannya adalah

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

Tabel ini berisi 103.129.000 baris.

Paket cepat dicari oleh ClientId dengan predikat residual pada tanggal tersebut tetapi perlu melakukan 96 pencarian untuk mengambil Amount. The <ParameterList>bagian dalam rencana adalah sebagai berikut.

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

Rencana lambat mencari berdasarkan tanggal dan memiliki pencarian untuk mengevaluasi predikat residual pada ClientId dan untuk mengambil jumlah (Diperkirakan 1 vs Sebenarnya 7.388.383). The <ParameterList>Bagian ini

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

Dalam kasus kedua ParameterCompiledValueini tidak kosong. SQL Server berhasil mengendus nilai yang digunakan dalam kueri.

Buku "SQL Server 2005 Praktis Mengatasi Masalah" memiliki ini untuk mengatakan tentang menggunakan variabel lokal

Menggunakan variabel lokal untuk mengalahkan parameter sniffing adalah trik yang cukup umum, tetapi OPTION (RECOMPILE)dan OPTION (OPTIMIZE FOR)petunjuk ... umumnya solusi yang lebih elegan dan sedikit kurang berisiko


Catatan

Dalam SQL Server 2005, kompilasi tingkat pernyataan memungkinkan untuk kompilasi pernyataan individu dalam prosedur tersimpan untuk ditunda sampai sebelum pelaksanaan pertama dari permintaan. Pada saat itu nilai variabel lokal akan diketahui. Secara teoritis SQL Server dapat memanfaatkan ini untuk mengendus nilai variabel lokal dengan cara yang sama seperti mengendus parameter. Namun karena itu umum untuk menggunakan variabel lokal untuk mengalahkan sniffing parameter dalam SQL Server 7.0 dan SQL Server 2000+, mengendus variabel lokal tidak diaktifkan di SQL Server 2005. Ini mungkin diaktifkan dalam rilis SQL Server di masa depan meskipun yang baik alasan untuk menggunakan salah satu opsi lain yang diuraikan dalam bab ini jika Anda punya pilihan.


Dari tes cepat akhir ini perilaku yang dijelaskan di atas masih sama pada 2008 dan 2012 dan variabel tidak diendus untuk kompilasi ditangguhkan tetapi hanya ketika OPTION RECOMPILEpetunjuk eksplisit digunakan.

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

Meskipun kompilasi ditangguhkan, variabel tidak terendus dan estimasi jumlah baris tidak akurat

Estimasi vs Aktual

Jadi saya berasumsi bahwa rencana lambat berkaitan dengan versi kueri parameterised.

Itu ParameterCompiledValuesama dengan ParameterRuntimeValueuntuk semua parameter jadi ini bukan sniffing parameter khas (di mana paket dikompilasi untuk satu set nilai kemudian jalankan untuk set nilai lain).

Masalahnya adalah bahwa paket yang dikompilasi untuk nilai parameter yang benar tidak pantas.

Anda kemungkinan akan mengalami masalah dengan tanggal yang disebutkan di sini dan di sini . Untuk tabel dengan 100 juta baris, Anda harus memasukkan (atau memodifikasi) 20 juta sebelum SQL Server akan secara otomatis memperbarui statistik untuk Anda. Tampaknya terakhir kali mereka diperbarui nol baris cocok dengan rentang tanggal dalam kueri tetapi sekarang 7 juta melakukannya.

Anda dapat menjadwalkan pembaruan statistik yang lebih sering, mempertimbangkan jejak bendera 2389 - 90atau menggunakan OPTIMIZE FOR UKNOWNsehingga hanya jatuh pada dugaan daripada dapat menggunakan statistik yang saat ini menyesatkan pada datetimekolom.

Ini mungkin tidak diperlukan dalam versi SQL Server berikutnya (setelah 2012). Sebuah Item Connect terkait mengandung respon menarik

Diposting oleh Microsoft pada 28/8/2012 di 13.35.
Kami telah melakukan peningkatan estimasi kardinalitas untuk rilis besar berikutnya yang pada dasarnya memperbaiki ini. Menantikan untuk detail setelah preview kami keluar. Eric

Peningkatan tahun 2014 ini dilihat oleh Benjamin Nevarez menjelang akhir artikel:

Pandangan Pertama pada Penaksir Kardinalitas SQL Server Baru .

Tampaknya penduga kardinalitas baru akan mundur dan menggunakan kerapatan rata-rata dalam kasus ini daripada memberikan perkiraan 1 baris.

Beberapa detail tambahan tentang penduga kardinalitas 2014 dan masalah utama yang menanjak di sini:

Fungsionalitas baru di SQL Server 2014 - Bagian 2 - Estimasi Kardinalitas Baru

Martin Smith
sumber
29

Jadi, pertanyaan saya adalah ini ... bagaimana bisa mengendus parameter menjadi menyalahkan ketika kita mendapatkan permintaan lambat yang sama pada cache rencana kosong ... seharusnya tidak ada parameter untuk mengendus?

Ketika SQL Server mengkompilasi kueri yang berisi nilai parameter, ia mengendus nilai spesifik dari parameter tersebut untuk estimasi kardinalitas (jumlah baris). Dalam kasus Anda, nilai-nilai tertentu dari @BeginDate, @EndDatedan @ClientIDdigunakan ketika memilih rencana eksekusi. Anda dapat menemukan detail lebih lanjut tentang mengendus parameter di sini dan di sini . Saya memberikan tautan latar belakang ini karena pertanyaan di atas membuat saya berpikir konsep ini dipahami secara tidak sempurna saat ini - selalu ada nilai parameter untuk mengendus ketika sebuah rencana dikompilasi.

Lagi pula, itu semua di luar intinya, karena mengendus parameter bukan masalah di sini seperti yang ditunjukkan Martin Smith. Pada saat kueri lambat dikompilasi, statistik menunjukkan tidak ada baris untuk nilai sniffed dari @BeginDatedan @EndDate:

Rencana lambat mengendus nilai

Nilai-nilai mengendus sangat baru, menunjukkan masalah kunci Martin menyebutkan. Karena pencarian indeks pada tanggal diperkirakan hanya menghasilkan satu baris, pengoptimal memilih rencana yang mendorong predikat ClientIDke operator Pencarian Kunci sebagai residu.

Perkiraan baris tunggal juga merupakan alasan pengoptimal berhenti mencari paket yang lebih baik, menghasilkan pesan Good Enough Plan Found. Estimasi total biaya rencana lambat dengan estimasi baris tunggal hanya 0,013136 unit biaya, jadi tidak ada gunanya mencoba menemukan sesuatu yang lebih baik. Kecuali, tentu saja, pencarian tersebut sebenarnya mengembalikan 7.388.383 baris daripada satu, menyebabkan jumlah Pencarian Kunci yang sama.

Statistik bisa sulit untuk tetap up-to-date dan berguna pada tabel besar dan partisi mempresentasikan tantangannya sendiri dalam hal itu. Saya sendiri belum berhasil, dengan bendera jejak 2389 dan 2390, tetapi Anda dipersilakan untuk mengujinya. Pembuatan SQL Server yang lebih baru (R2 SP1 dan yang lebih baru) memiliki pembaruan statistik dinamis yang tersedia, tetapi pembaruan statistik per-partisi ini masih belum diimplementasikan. Sementara itu, Anda mungkin ingin menjadwalkan pembaruan statistik manual setiap kali Anda membuat perubahan signifikan pada tabel ini.

Untuk permintaan khusus ini, saya akan berpikir tentang menerapkan indeks yang disarankan oleh pengoptimal selama kompilasi rencana permintaan cepat:

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

Indeks harus disejajarkan dengan partisi, dengan ON PartitionSchemeName (PostedDate)klausa, tetapi intinya adalah bahwa menyediakan jalur akses data yang jelas terbaik akan membantu pengoptimal menghindari pilihan rencana yang buruk, tanpa menggunakan OPTIMIZE FOR UNKNOWNpetunjuk atau solusi kuno seperti menggunakan variabel lokal.

Dengan indeks yang ditingkatkan, Pencarian Kunci untuk mengambil Amountkolom akan dihilangkan, prosesor kueri masih dapat melakukan penghapusan partisi dinamis, dan menggunakan pencarian untuk menemukan ClientIDrentang tanggal dan tertentu .

Paul White mengatakan GoFundMonica
sumber
Seandainya saya bisa menandai dua jawaban sebagai benar, tapi sekali lagi, terima kasih atas info tambahan - sangat instruktif.
RThomas
1
Sudah beberapa tahun sejak saya memposting ini ... tapi saya hanya ingin memberi tahu Anda. Saya masih menggunakan istilah "dipahami dengan tidak sempurna" sepanjang waktu yang menakutkan, dan saya selalu memikirkan Paul White ketika saya melakukannya. Membuatku tertawa setiap saat.
RThomas
0

Saya memiliki masalah yang sama persis di mana prosedur tersimpan menjadi lambat, dan OPTIMIZE FOR UNKNOWNdan RECOMPILEpermintaan petunjuk menyelesaikan kelambatan dan mempercepat waktu eksekusi. Namun, dua metode berikut ini tidak mempengaruhi lambatnya prosedur yang tersimpan: (i) Menghapus cache (ii) menggunakan WITH RECOMPILE. Jadi, seperti yang Anda katakan, itu bukan parameter sniffing.

Bendera jejak 2389 dan 2390 tidak membantu juga. Hanya memperbarui statistik ( EXEC sp_updatestats) yang melakukannya untuk saya.

aali
sumber