Ketika saya menambahkan dua kolom ke pilih saya kueri tidak merespons. Jenis kolomnya adalah nvarchar(2000)
. Agak aneh.
- Versi SQL Server adalah 2014.
- Hanya ada satu indeks utama.
- Seluruh catatan hanya 1000 baris.
Berikut adalah rencana eksekusi sebelumnya ( XML showplan ):
Rencana eksekusi setelah ( XML showplan ):
Ini pertanyaannya:
select top(100)
Batch_Tasks_Queue.id,
btq.id,
Batch_Tasks_Queue.[Parameters], -- this field
btq.[Parameters] -- and this field
from
Batch_Tasks_Queue with(nolock)
inner join Batch_Tasks_Queue btq with(nolock) on Batch_Tasks_Queue.Start_Time < btq.Start_Time
and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
and Batch_Tasks_Queue.id <> btq.id
and btq.Start_Time is not null
and btq.State in (3, 4)
where
Batch_Tasks_Queue.Start_Time is not null
and Batch_Tasks_Queue.State in (3, 4)
and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)
order by
Batch_Tasks_Queue.Start_Time desc
Seluruh hasil hitungan adalah 17 baris. Data kotor (nolock hint) tidak penting.
Berikut adalah struktur tabelnya:
CREATE TABLE [dbo].[Batch_Tasks_Queue](
[Id] [int] NOT NULL,
[OBJ_VERSION] [numeric](8, 0) NOT NULL,
[Operation_Type] [numeric](2, 0) NULL,
[Request_Time] [datetime] NOT NULL,
[Description] [varchar](1000) NULL,
[State] [numeric](1, 0) NOT NULL,
[Start_Time] [datetime] NULL,
[Finish_Time] [datetime] NULL,
[Parameters] [nvarchar](2000) NULL,
[Response] [nvarchar](max) NULL,
[Billing_UserId] [int] NOT NULL,
[Planned_Start_Time] [datetime] NULL,
[Input_FileId] [uniqueidentifier] NULL,
[Output_FileId] [uniqueidentifier] NULL,
[PRIORITY] [numeric](2, 0) NULL,
[EXECUTE_SEQ] [numeric](2, 0) NULL,
[View_Access] [numeric](1, 0) NULL,
[Seeing] [numeric](1, 0) NULL,
CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] WITH NOCHECK ADD CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO
sql-server
query-performance
sql-server-2014
Hamid Fathi
sumber
sumber
Jawaban:
Ringkasan
Masalah utama adalah:
Detail
Kedua paket ini pada dasarnya sangat mirip, meskipun kinerjanya mungkin sangat berbeda:
Rencanakan dengan kolom tambahan
Mengambil satu dengan kolom tambahan yang tidak lengkap dalam waktu yang wajar terlebih dahulu:
Fitur yang menarik adalah:
Start_Time
bukan nol,State
adalah 3 atau 4, danOperation_Type
merupakan salah satu nilai yang terdaftar. Tabel dipindai sepenuhnya satu kali, dengan setiap baris diuji terhadap predikat yang disebutkan. Hanya baris yang lulus semua tes yang mengalir ke Sort. Pengoptimal memperkirakan 38.283 baris akan memenuhi syarat.Start_Time DESC
. Ini adalah urutan presentasi final yang diminta oleh kueri.Start_Time
tidak nol danState
3 atau 4. Ini diperkirakan menghasilkan 400.875 baris pada setiap iterasi. Lebih dari 94.2791 iterasi, jumlah baris hampir 38 juta.Operation_Type
cocok, bahwaStart_Time
dari simpul 4 kurang dariStart_Time
dari simpul 5, bahwaStart_Time
dari simpul 5 kurang dariFinish_Time
dari simpul 4, dan bahwa duaId
nilai tidak cocok.Inefisiensi besar jelas pada langkah 6 dan 7 di atas. Pemindaian penuh tabel pada simpul 5 untuk setiap iterasi hanya sedikit masuk akal jika hanya terjadi 94 kali seperti yang diprediksi oleh optimizer. Perbandingan ~ 38 juta per-baris pada node 2 juga merupakan biaya yang besar.
Yang terpenting, estimasi sasaran baris 93/94 juga sangat mungkin salah, karena bergantung pada distribusi nilai. Pengoptimal mengasumsikan distribusi seragam tanpa adanya informasi yang lebih rinci. Secara sederhana, ini berarti bahwa jika 1% dari baris dalam tabel diharapkan untuk memenuhi syarat, alasan pengoptimal untuk menemukan 1 baris yang cocok, itu perlu membaca 100 baris.
Jika Anda menjalankan kueri ini hingga selesai (yang mungkin membutuhkan waktu sangat lama), kemungkinan besar Anda akan menemukan bahwa lebih dari 93/94 baris harus dibaca dari Sortir untuk akhirnya menghasilkan 100 baris. Dalam kasus terburuk, baris ke-100 akan ditemukan menggunakan baris terakhir dari Sort. Dengan asumsi estimasi optimizer pada node 4 benar, ini berarti menjalankan Scan at node 5 38.284 kali, dengan total sekitar 15 miliar baris. Bisa jadi lebih jika estimasi Pindai juga tidak aktif.
Rencana eksekusi ini juga termasuk peringatan indeks yang hilang:
Pengoptimal memberi tahu Anda bahwa menambahkan indeks ke tabel akan meningkatkan kinerja.
Rencanakan tanpa kolom tambahan
Ini pada dasarnya adalah rencana yang sama persis seperti yang sebelumnya, dengan penambahan Index Spool pada node 6 dan Filter pada node 5. Perbedaan penting adalah:
Operation_Type
danStart_Time
, denganId
sebagai kolom non-kunci.Operation_Type
,Start_Time
,Finish_Time
, danId
dari scan pada node 4 dilewatkan ke cabang batin-side sebagai referensi luar.Operation_Type
cocok dengan nilai referensi luar saat ini, danStart_Time
berada dalam kisaran yang ditentukan oleh referensi luarStart_Time
danFinish_Time
.Id
nilai dari Spool Indeks untuk ketidaksetaraan terhadap nilai referensi luar saat ini dariId
.Perbaikan utama adalah:
Operation_Type
,Start_Time
) denganId
sebagai kolom yang disertakan memungkinkan loop bersarang indeks bergabung. Indeks digunakan untuk mencari baris yang cocok pada setiap iterasi daripada memindai seluruh tabel setiap kali.Seperti sebelumnya, pengoptimal menyertakan peringatan tentang indeks yang hilang:
Kesimpulan
Paket tanpa kolom tambahan lebih cepat karena pengoptimal memilih untuk membuat indeks sementara untuk Anda.
Rencana dengan kolom tambahan akan membuat indeks sementara lebih mahal untuk dibangun. The
[Parameters
Kolom] adalahnvarchar(2000)
, yang akan menambahkan hingga 4000 byte untuk setiap baris dari indeks. Biaya tambahan cukup untuk meyakinkan pengoptimal bahwa membangun indeks sementara pada setiap eksekusi tidak akan membayar sendiri.Pengoptimal memperingatkan dalam kedua kasus bahwa indeks permanen akan menjadi solusi yang lebih baik. Komposisi indeks yang ideal tergantung pada beban kerja Anda yang lebih luas. Untuk permintaan khusus ini, indeks yang disarankan adalah titik awal yang masuk akal, tetapi Anda harus memahami manfaat dan biaya yang terlibat.
Rekomendasi
Berbagai kemungkinan indeks akan bermanfaat untuk kueri ini. Hal penting yang perlu diperhatikan adalah diperlukan semacam indeks yang tidak tercakup. Dari informasi yang diberikan, indeks yang masuk akal menurut saya adalah:
Saya juga akan tergoda untuk mengatur kueri sedikit lebih baik, dan menunda mencari
[Parameters]
kolom lebar dalam indeks berkerumun sampai setelah 100 baris teratas telah ditemukan (menggunakanId
sebagai kunci):Jika
[Parameters]
kolom tidak diperlukan, kueri dapat disederhanakan menjadi:The
FORCESEEK
petunjuk yang ada untuk membantu memastikan optimizer memilih sebuah loop bersarang diindeks berencana (ada godaan berbasis biaya untuk optimizer untuk memilih hash atau (banyak-banyak) merge bergabung sebaliknya, yang cenderung untuk tidak bekerja dengan baik dengan jenis dalam praktek. Keduanya berakhir dengan residu besar; banyak item per ember dalam kasus hash, dan banyak mundur untuk penggabungan).Alternatif
Jika kueri (termasuk nilai spesifiknya) sangat penting untuk kinerja baca, saya akan mempertimbangkan dua indeks yang difilter sebagai gantinya:
Untuk kueri yang tidak memerlukan
[Parameters]
kolom, taksiran paket menggunakan indeks yang difilter adalah:Pemindaian indeks secara otomatis mengembalikan semua baris yang memenuhi syarat tanpa mengevaluasi predikat tambahan apa pun. Untuk setiap iterasi dari loop bersarang indeks, pencarian indeks melakukan dua operasi pencarian:
Operation_Type
danState
= 3, kemudian mencari rentangStart_Time
nilai, predikat residual padaId
ketidaksetaraan.Operation_Type
danState
= 4, kemudian mencari rentangStart_Time
nilai, predikat residual padaId
ketidaksetaraan.Di mana
[Parameters]
kolom diperlukan, rencana kueri hanya menambahkan maksimum 100 pencarian tunggal untuk setiap tabel:Sebagai catatan akhir, Anda harus mempertimbangkan untuk menggunakan tipe integer standar bawaan alih-alih yang
numeric
berlaku.sumber
Harap buat indeks berikut:
sumber