Database
 sql >> Teknologi Basis Data >  >> RDS >> Database

Kasus Perkiraan Kardinalitas Red Herring

Anda bekerja dengan pengembang yang melaporkan kinerja lambat untuk panggilan prosedur tersimpan berikut:

EXEC [dbo].[charge_by_date] '2/28/2013';

Anda menanyakan masalah apa yang dilihat pengembang, tetapi satu-satunya informasi tambahan yang Anda dengar adalah bahwa itu "berjalan lambat". Jadi Anda melompat pada contoh SQL Server dan melihat aktual rencana eksekusi. Anda melakukan ini karena Anda tertarik tidak hanya pada tampilan rencana eksekusi tetapi juga perkiraan jumlah baris aktual versus rencana:

Melihat pertama hanya pada operator paket, Anda dapat melihat beberapa detail penting:

  • Ada peringatan di operator root
  • Ada pemindaian tabel untuk kedua tabel yang direferensikan pada tingkat daun (charge_jan dan charge_feb) dan Anda bertanya-tanya mengapa keduanya masih menumpuk dan tidak memiliki indeks berkerumun
  • Anda melihat bahwa hanya ada baris yang mengalir melalui tabel charge_feb dan bukan tabel charge_jan
  • Anda melihat zona paralel dalam rencana

Adapun peringatan di iterator root, Anda mengarahkan kursor ke atasnya dan melihat bahwa ada peringatan indeks yang hilang dengan rekomendasi untuk indeks berikut:

CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[charge_feb] ([charge_dt])
INCLUDE ([charge_no])
GO
 
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[charge_jan] ([charge_dt])
INCLUDE ([charge_no])
GO

Anda bertanya kepada pengembang basis data asli mengapa tidak ada indeks berkerumun, dan jawabannya adalah “Saya tidak tahu.”

Melanjutkan penyelidikan sebelum membuat perubahan apa pun, Anda melihat tab Plan Tree di SQL Sentry Plan Explorer dan Anda memang melihat bahwa ada kemiringan yang signifikan antara baris yang diperkirakan versus yang sebenarnya untuk salah satu tabel:

Tampaknya ada dua masalah:

  • Perkiraan rendah untuk baris dalam pemindaian tabel charge_jan
  • Perkiraan berlebihan untuk baris dalam pemindaian tabel charge_feb

Jadi perkiraan kardinalitas adalah miring, dan Anda bertanya-tanya apakah ini terkait dengan parameter sniffing. Anda memutuskan untuk memeriksa nilai kompilasi parameter dan membandingkannya dengan nilai runtime parameter, yang dapat Anda lihat di tab Parameter:

Memang ada perbedaan antara nilai runtime dan nilai yang dikompilasi. Anda menyalin database ke lingkungan pengujian seperti prod lalu menguji eksekusi prosedur tersimpan dengan nilai runtime 28/2/2013 terlebih dahulu dan kemudian 1/31/2013 setelahnya.

Paket 28/2/2013 dan 31/1/2013 memiliki bentuk yang identik tetapi aliran data aktualnya berbeda. Rencana dan perkiraan kardinalitas 28/2/2013 adalah sebagai berikut:

Dan sementara rencana 28/2/2013 tidak menunjukkan masalah estimasi kardinalitas, rencana 31/31/2013 menunjukkan:

Jadi, paket kedua menunjukkan perkiraan yang sama di atas dan di bawah, hanya terbalik dari paket awal yang Anda lihat.

Anda memutuskan untuk menambahkan indeks yang disarankan ke lingkungan pengujian seperti prod untuk tabel charge_jan dan charge_feb dan melihat apakah itu membantu sama sekali. Menjalankan prosedur tersimpan dalam pesanan Januari / Februari, Anda akan melihat bentuk rencana baru berikut dan perkiraan kardinalitas terkait:

Paket baru menggunakan operasi Pencarian Indeks dari setiap tabel, tetapi Anda masih melihat nol baris mengalir dari satu tabel dan bukan tabel lainnya, dan Anda masih melihat perkiraan kardinalitas miring berdasarkan parameter sniffing ketika nilai runtime berada di bulan yang berbeda dari kompilasi nilai waktu.

Tim Anda memiliki kebijakan untuk tidak menambahkan indeks tanpa bukti manfaat yang memadai dan pengujian regresi terkait. Anda memutuskan, untuk saat ini, untuk menghapus indeks nonclustered yang baru saja Anda buat. Meskipun Anda tidak segera mengatasi berkelompok yang hilang indeks, Anda memutuskan untuk mengurusnya nanti.

Pada titik ini Anda menyadari bahwa Anda perlu melihat lebih jauh ke dalam definisi prosedur tersimpan, yaitu sebagai berikut:

CREATE PROCEDURE dbo.charge_by_date
  @charge_dt datetime
AS
  SELECT charge_no
  FROM dbo.charge_view
  WHERE charge_dt = @charge_dt
GO

Selanjutnya Anda melihat definisi objek charge_view:

CREATE VIEW charge_view
AS
  SELECT *
  FROM [charge_jan]
  UNION ALL
  SELECT *
  FROM [charge_feb]
GO

Referensi tampilan membebankan data yang dipisahkan ke dalam tabel yang berbeda menurut tanggal. Dan kemudian Anda bertanya-tanya apakah rencana eksekusi kueri kedua dapat dicegah dengan mengubah definisi prosedur tersimpan.

Mungkin jika pengoptimal mengetahui pada waktu proses berapa nilainya, masalah perkiraan kardinalitas akan hilang dan meningkatkan kinerja secara keseluruhan?

Anda melanjutkan dan mendefinisikan kembali panggilan prosedur tersimpan sebagai berikut, menambahkan petunjuk RECOMPILE (mengetahui bahwa Anda juga pernah mendengar bahwa ini dapat meningkatkan penggunaan CPU, tetapi karena ini adalah lingkungan pengujian, Anda merasa aman untuk mencobanya):

ALTER PROCEDURE charge_by_date
  @charge_dt datetime
AS
  SELECT charge_no
  FROM dbo.charge_view
  WHERE charge_dt = @charge_dt
  OPTION (RECOMPILE);
GO

Anda kemudian menjalankan kembali prosedur tersimpan menggunakan nilai 1/31/2013 dan kemudian nilai 28/2/2013.

Bentuk denah tetap sama, tetapi sekarang masalah perkiraan kardinalitas telah dihapus.

Data estimasi kardinalitas 1/31/2013 menunjukkan:

Dan data estimasi kardinalitas 28/2/2013 menunjukkan:

Itu membuat Anda senang untuk sesaat, tetapi kemudian Anda menyadari durasi keseluruhan eksekusi kueri tampaknya relatif sama seperti sebelumnya. Anda mulai ragu bahwa pengembang akan senang dengan hasil Anda. Anda telah memecahkan perkiraan perkiraan kardinalitas, tetapi tanpa peningkatan kinerja yang diharapkan, Anda tidak yakin apakah Anda telah membantu dengan cara apa pun yang berarti.

Pada titik inilah Anda menyadari bahwa rencana eksekusi kueri hanyalah sebagian dari informasi yang mungkin Anda perlukan, sehingga Anda memperluas eksplorasi Anda lebih jauh dengan melihat tab Tabel I/O. Anda melihat output berikut untuk eksekusi 1/31/2013:

Dan untuk eksekusi 28/2/2013 Anda melihat data serupa:

Pada titik itulah Anda bertanya-tanya apakah operasi akses data untuk keduanya tabel diperlukan dalam setiap rencana. Jika pengoptimal tahu Anda hanya membutuhkan baris Januari, mengapa mengakses Februari sama sekali, dan sebaliknya? Anda juga ingat bahwa pengoptimal kueri tidak menjamin bahwa tidak ada baris aktual dari bulan lain dalam tabel "salah" kecuali jaminan tersebut dibuat secara eksplisit melalui batasan pada tabel itu sendiri.

Anda memeriksa definisi tabel melalui sp_help untuk setiap tabel dan Anda tidak melihat batasan apa pun yang ditentukan untuk kedua tabel.

Jadi sebagai pengujian, Anda menambahkan dua batasan berikut:

ALTER TABLE [dbo].[charge_jan]
  ADD CONSTRAINT charge_jan_chk CHECK
  (charge_dt >= '1/1/2013' AND charge_dt < '2/1/2013');
GO
 
ALTER TABLE [dbo].[charge_feb]
  ADD CONSTRAINT charge_feb_chk CHECK
  (charge_dt >= '2/1/2013' AND charge_dt < '3/1/2013');
GO

Anda menjalankan kembali prosedur tersimpan dan melihat bentuk rencana dan perkiraan kardinalitas berikut.

31/01/2013 eksekusi:

28/2/2013 eksekusi:

Melihat Tabel I/O lagi, Anda melihat output berikut untuk eksekusi 1/31/2013:

Dan untuk eksekusi 28/2/2013 Anda melihat data yang serupa, tetapi untuk tabel charge_feb:

Mengingat bahwa Anda memiliki RECOMPILE masih dalam definisi prosedur tersimpan, Anda mencoba menghapusnya dan melihat apakah Anda melihat efek yang sama. Setelah melakukan ini, Anda melihat akses dua tabel kembali, tetapi tanpa pembacaan logis aktual untuk tabel yang tidak memiliki baris di dalamnya (dibandingkan dengan paket awal tanpa batasan). Misalnya, eksekusi 1/31/2013 menunjukkan output Tabel I/O berikut:

Anda memutuskan untuk melanjutkan dengan pengujian beban kendala CHECK yang baru dan solusi RECOMPILE, menghapus akses tabel sepenuhnya dari paket (dan operator paket terkait). Anda juga mempersiapkan diri untuk debat tentang kunci indeks berkerumun dan indeks non-cluster pendukung yang sesuai yang akan mengakomodasi serangkaian beban kerja yang lebih luas yang saat ini mengakses tabel terkait.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 3 Statistik I/O Buruk yang Menunda Kinerja Kueri SQL

  2. Dasar-dasar ekspresi tabel, Bagian 1

  3. Alat online untuk mencoba desain dan kueri SQL

  4. Hash Bergabung di Kolom Nullable

  5. Apa itu indeks dalam SQL?