SQL Server 2016 Bad Query Plan mengunci DB seminggu sekali

16

Sekali seminggu, selama 5 minggu terakhir, sekitar waktu yang sama (dini hari, mungkin didasarkan pada aktivitas pengguna ketika orang-orang mulai menggunakannya), SQL Server 2016 (AWS RDS, mirrored) mulai menghitung banyak pertanyaan.

STATISTIK PEMBARUAN pada semua tabel selalu segera memperbaikinya.

Setelah pertama kali, saya membuatnya memperbarui semua statistik pada semua tabel setiap malam (bukan mingguan), tetapi itu masih terjadi, (sekitar 8 jam setelah statistik pembaruan berjalan, tetapi tidak setiap hari menjalankan statistik).

Terakhir kali ini, saya mengaktifkan Query Store untuk melihat apakah saya dapat menemukan kueri / paket kueri tertentu itu. Saya pikir saya bisa mempersempitnya menjadi satu:

Paket kueri salah

Setelah menemukan kueri itu, saya menambahkan indeks yang direkomendasikan yang tidak ada dari kueri yang tidak sering digunakan ini (tetapi yang menyentuh banyak tabel yang sering digunakan).

Paket permintaan yang buruk adalah melakukan pemindaian indeks (di atas meja dengan hanya 10rb baris). Paket permintaan lain yang dikembalikan dalam milidetik, digunakan untuk melakukan pemindaian yang sama. Paket permintaan terbaru, setelah membuat indeks baru hanya mencari. Tetapi bahkan tanpa indeks itu, 99% dari waktu, itu kembali dalam beberapa milidetik, tetapi kemudian, setiap minggu, itu akan membutuhkan> 40 detik.

Ini mulai terjadi setelah pindah ke SQL Server 2016 dari 2012.

DBCC CHECKDB tidak mengembalikan kesalahan.

  1. Akankah indeks baru memperbaiki masalah, membuatnya tidak pernah memilih rencana buruk lagi?
  2. Haruskah saya "memaksa" rencana yang berfungsi dengan baik sekarang?
  3. Bagaimana saya memastikan ini tidak terjadi pada permintaan / rencana lain?
  4. Apakah ini gejala masalah yang lebih besar?

Indeks yang baru saya tambahkan:

CREATE NONCLUSTERED INDEX idx_AppointmetnAttendee_AttendeeType
ON [dbo].[AppointmentAttendee] ([UserID],[AttendeeType])

CREATE NONCLUSTERED INDEX [idx_appointment_start] ON [dbo].[Appointment]
(
    [ProjectID] ASC,
    [Start] ASC
)
INCLUDE (   [ID],
    [AllDay],
    [End],
    [Location],
    [Notes],
    [Title],
    [CreatedByID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Teks kueri lengkap:

https://pastebin.com/Z5szPBfu (yang dihasilkan LINQ, saya dapat / seharusnya dapat mengoptimalkan kolom yang dipilih, tetapi seharusnya tidak relevan dengan masalah ini)

Nama Terdengar Profesional
sumber
Saya hanya memperhatikan bahwa pemindaian pada paket Sebelumnya yang tidak habis waktu berada di meja yang berbeda, dengan ukuran yang sama. Pengangkatan: 11931 baris, Pengangkatan Peserta: 11937 baris.
Nama Terdengar Profesional

Jawaban:

16

Saya akan menjawab pertanyaan Anda dalam urutan yang berbeda dari yang Anda tanyakan.

4. Apakah ini merupakan gejala dari masalah yang lebih besar?

The estimator kardinalitas baru di SQL Server 2016 dapat memberikan kontribusi terhadap masalah. SQL Server 2012 menggunakan legacy CE dan Anda tidak mengalami masalah pada versi itu. Penaksir kardinalitas baru membuat asumsi yang berbeda tentang data Anda dan dapat menghasilkan rencana kueri yang berbeda untuk SQL yang sama. Anda mungkin mengalami kinerja yang lebih baik untuk beberapa permintaan dengan legacy CE tergantung pada permintaan dan data Anda. Jadi, beberapa bagian dari model data Anda mungkin bukan yang paling cocok untuk CE yang baru. Tidak apa-apa, tetapi Anda mungkin perlu mengatasi CE baru untuk saat ini.

Saya juga akan khawatir dengan kinerja permintaan yang tidak konsisten bahkan dengan pembaruan statistik harian. Satu hal penting yang perlu diperhatikan adalah bahwa mengumpulkan statistik pada semua tabel akan secara efektif menghapus semua rencana kueri dari cache, sehingga Anda dapat memiliki masalah dengan statistik atau mungkin harus dilakukan dengan sniffing parameter. Sulit untuk menentukan tanpa banyak informasi tentang model data Anda, laju perubahan data, kebijakan memperbarui statistik, bagaimana Anda memanggil kode Anda, dll. SQL Server 2016 memang menawarkan beberapa pengaturan level database untuk sniffing parameter yang dapat membantu , tapi itu bisa memengaruhi seluruh aplikasi Anda, dan bukan hanya satu permintaan yang bermasalah.

Saya akan membuang contoh skenario yang dapat menyebabkan perilaku ini. Kamu berkata:

Beberapa pengguna dapat memiliki 1 catatan izin, beberapa, hingga 20rb.

Misalkan Anda mengumpulkan statistik pada semua tabel yang menghapus semua rencana kueri. Bergantung pada faktor-faktor yang disebutkan di atas, jika kueri pertama hari itu terhadap pengguna dengan hanya 1 catatan izin maka SQL Server dapat menyimpan rencana yang bekerja dengan baik untuk pengguna dengan 1 catatan tetapi bekerja sangat buruk dengan pengguna dengan catatan 20k. Jika kueri pertama hari itu terhadap pengguna dengan catatan 20k maka Anda mungkin mendapatkan rencana yang baik untuk catatan 20k. Ketika kode dijalankan terhadap pengguna dengan 1 catatan itu mungkin bukan permintaan yang paling optimal tetapi mungkin masih selesai dalam ms. Ini benar-benar terdengar seperti parameter sniffing. Ini menjelaskan mengapa Anda tidak selalu melihat masalah atau mengapa terkadang dibutuhkan waktu berjam-jam untuk muncul.

1. Apakah indeks baru akan memperbaiki masalah, sehingga tidak pernah memilih rencana yang buruk lagi?

Saya pikir salah satu indeks yang Anda tambahkan akan mencegah masalah karena mengakses data yang diperlukan melalui indeks akan lebih murah daripada melakukan pemindaian indeks berkerumun terhadap tabel, terutama ketika pemindaian tidak dapat berakhir lebih awal. Mari memperbesar bagian buruk dari rencana kueri:

rencana kueri yang buruk

SQL Server memperkirakan bahwa hanya satu baris yang akan dikembalikan dari join on [Permission]dan [Project]. Untuk setiap baris di input luar itu akan melakukan pemindaian indeks berkerumun [Appointment]. Semua baris akan dipindai dari tabel ini, tetapi hanya mereka yang cocok dengan penyaringan [Start]akan dikembalikan ke operator bergabung. Di dalam operator gabungan hasilnya semakin berkurang.

Paket kueri yang dijelaskan di atas dapat diterima jika hanya ada satu baris yang dikirim ke input luar dari gabungan. Namun, jika perkiraan kardinalitas dari gabungan salah dan kami mendapatkan, katakanlah, 1000 baris, maka SQL Server akan melakukan 1000 scan indeks berkerumun [Appointment]. Kinerja rencana kueri sangat sensitif terhadap masalah estimasi.

Cara paling langsung untuk tidak pernah mendapatkan rencana permintaan itu lagi adalah dengan membuat indeks penutup terhadap [Appointment]tabel. Sesuatu seperti indeks [ProjectId]dan [Start]harus melakukannya. Sepertinya ini persis [idx_appointment_start]indeks yang Anda buat untuk mengatasi masalah ini. Cara lain untuk mencegah SQL server memilih rencana kueri adalah untuk memperbaiki perkiraan kardinalitas dari gabungan [Permission]dan [Project]. Cara-cara khas untuk melakukan itu termasuk mengubah kode, memperbarui statistik, menggunakan legacy CE, membuat statistik multi-kolom, memberikan SQL Server informasi lebih lanjut tentang variabel lokal seperti dengan sebuah RECOMPILEpetunjuk, atau mematerialisasi baris-baris tersebut menjadi tabel temp. Banyak dari teknik-teknik itu bukan pendekatan yang baik ketika Anda membutuhkan waktu respons tingkat ms atau harus menulis kode melalui ORM.

Indeks yang Anda buat [AppointmentAttendee]bukan cara langsung untuk mengatasi masalah. Namun, Anda akan mendapatkan statistik multi-kolom pada indeks dan statistik tersebut dapat menghambat rencana kueri yang buruk. Indeks mungkin menyediakan cara yang lebih efisien untuk mengakses data yang juga dapat mencegah rencana kueri yang buruk, tetapi saya tidak berpikir ada semacam jaminan bahwa itu tidak akan terjadi lagi hanya dengan indeks aktif [AppointmentAttendee].

3. Bagaimana saya memastikan ini tidak terjadi pada permintaan / rencana lain?

Saya mengerti mengapa Anda mengajukan pertanyaan ini, tetapi ini adalah pertanyaan yang sangat luas. Satu-satunya saran saya adalah mencoba untuk lebih memahami akar penyebab ketidakstabilan rencana kueri, untuk memvalidasi bahwa Anda memiliki indeks yang tepat dibuat untuk beban kerja Anda, dan untuk hati-hati menguji dan memantau beban kerja Anda. Microsoft memiliki beberapa saran umum tentang cara menangani regresi rencana kueri yang disebabkan oleh CE baru di SQL Server 2016:

Alur kerja yang disarankan untuk memutakhirkan prosesor kueri ke versi terbaru dari kode adalah:

  1. Memutakhirkan database ke SQL Server 2016 tanpa mengubah tingkat kompatibilitas database (pertahankan di level sebelumnya)

  2. Aktifkan toko permintaan pada database. Untuk informasi lebih lanjut tentang mengaktifkan dan menggunakan toko kueri, lihat Memantau Kinerja Dengan Menggunakan Toko Kueri.

  3. Tunggu cukup waktu untuk mengumpulkan data representatif dari beban kerja.

  4. Ubah tingkat kompatibilitas database menjadi 130

  5. Menggunakan SQL Server Management Studio, mengevaluasi apakah ada regresi kinerja pada permintaan tertentu setelah perubahan tingkat kompatibilitas

  6. Untuk kasus di mana ada regresi, paksa rencana sebelumnya di toko kueri.

  7. Jika ada rencana kueri yang gagal dipaksakan atau jika kinerja masih tidak mencukupi, pertimbangkan untuk mengembalikan level kompatibilitas ke pengaturan sebelumnya dan kemudian menggunakan Dukungan Pelanggan Microsoft.

Saya tidak mengatakan bahwa Anda perlu menurunkan versi ke SQL Server 2012 dan memulai dari awal, tetapi teknik umum yang dijelaskan mungkin berguna untuk Anda.

2. Haruskah saya "memaksa" rencana yang berfungsi dengan baik sekarang?

Semuanya terserah Anda. Jika Anda yakin bahwa Anda memiliki paket kueri yang berfungsi dengan baik untuk semua parameter input yang mungkin, merasa nyaman dengan fungsionalitas toko kueri, dan menginginkan ketenangan pikiran yang disertai dengan pemaksaan rencana kueri kemudian lakukan. Memaksa rencana kueri yang mengalami regresi adalah bagian dari kebijakan peningkatan yang disarankan Microsoft untuk SQL Server 2016.

Joe Obbish
sumber