Menurut jawaban ini , kecuali indeks dibangun di atas kolom yang digunakan untuk membatasi, kueri tidak akan mendapat manfaat dari indeks.
Saya memiliki definisi ini:
CREATE TABLE [dbo].[JobItems] (
[ItemId] UNIQUEIDENTIFIER NOT NULL,
[ItemState] INT NOT NULL,
[ItemPriority] INT NOT NULL,
[CreationTime] DATETIME NULL DEFAULT GETUTCDATE(),
[LastAccessTime] DATETIME NULL DEFAULT GETUTCDATE(),
-- other columns
);
CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
ON [dbo].[JobItems]([ItemId] ASC);
GO
CREATE INDEX [GetItemToProcessIndex]
ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
INCLUDE (LastAccessTime);
GO
dan pertanyaan ini:
UPDATE TOP (150) JobItems
SET ItemState = 17
WHERE
ItemState IN (3, 9, 10)
AND LastAccessTime < DATEADD (day, -2, GETUTCDATE())
AND CreationTime < DATEADD (day, -2, GETUTCDATE());
Saya meninjau rencana yang sebenarnya, dan hanya ada satu pencarian indeks dengan predikat persis seperti di WHERE
- tidak ada "pencarian bookmark" tambahan untuk mengambil LastAccessTime
meskipun yang terakhir hanya "termasuk" ke dalam indeks, bukan bagian dari indeks.
Menurut saya perilaku ini bertentangan dengan aturan bahwa kolom harus menjadi bagian dari indeks, dan bukan hanya "termasuk".
Apakah perilaku yang saya amati itu benar? Bagaimana saya bisa tahu sebelumnya jika WHERE
manfaat saya dari kolom yang disertakan atau membutuhkan kolom untuk menjadi bagian dari indeks?
sumber
ItemState
nilai, tetapi pencarian tidak akan seefisien jika Indeks Anda disusun sebagai berikut(ItemState, CreationTime, LastAccessTime)
(ItemState, CreationTime) INCLUDE (LastAccessTime)
(a,b)
bukan yang terbaik untuk kueriSELECT a FROM t WHERE b=5;
dan indeks aktif(b) INCLUDE (a)
jauh lebih baik.Jawaban:
Predikat Anda berbeda dengan Predikat Pencarian Anda.
Prediksi Seek digunakan untuk mencari data yang diurutkan dalam indeks. Dalam hal ini, itu akan melakukan tiga pencarian, satu untuk setiap ItemState yang Anda minati. Selain itu, data dalam urutan ItemPriority, jadi tidak ada lagi operasi "Seek" yang dapat dilakukan.
Tetapi sebelum data dikembalikan, ia memeriksa setiap baris menggunakan Predikat, yang saya sebut sebagai Predikat Residual. Ini dilakukan pada hasil Predikat Mencari.
Kolom apa pun yang disertakan bukan bagian dari data yang dipesan, tetapi dapat digunakan untuk memenuhi Predikat Residual, tanpa harus melakukan Pencarian tambahan.
Anda dapat melihat materi yang saya tulis tentang Sargability ini. Periksa sesi di SQLBits khususnya, di http://bit.ly/Sargability
Sunting: Untuk menunjukkan dampak Residual lebih baik, jalankan kueri menggunakan yang tidak berdokumen
OPTION (QUERYTRACEON 9130)
, yang akan memisahkan Residual menjadi operator Filter terpisah (yang sebenarnya merupakan versi paket sebelumnya sebelum residu dipindahkan ke operator Seek). Itu jelas menunjukkan dampak dari Seek yang tidak efektif, dengan jumlah baris yang diteruskan ke Filter.Perlu juga dicatat bahwa karena klausa IN pada ItemState, data yang dikirimkan ke kiri sebenarnya dalam urutan ItemState, bukan dalam urutan ItemPriority. Indeks komposit pada ItemState diikuti oleh salah satu tanggal (mis. (ItemState, LastAccessTime)) dapat digunakan untuk memiliki tiga pencarian (perhatikan Prediksi Pencarian menunjukkan tiga pencarian dalam satu operator Mencari), masing-masing terhadap dua level, menghasilkan data yang masih dalam urutan ItemState (mis., ItemState = 3 dan LastAccessTime kurang dari sesuatu, maka ItemState = 9 dan LastAccessTime kurang dari sesuatu, dan kemudian ItemState = 10 dan LastAccessTime kurang dari sesuatu).
Indeks pada (ItemState, LastAccesTime, CreationTime) tidak akan lebih berguna dari pada (ItemState, LastAccessTime) karena tingkat CreationTime hanya berguna jika pencarian Anda untuk kombinasi ItemState dan LastAccessTime tertentu, bukan rentang. Seperti bagaimana buku telepon tidak dalam urutan FirstName jika Anda tertarik dengan Nama keluarga dimulai pada F.
Jika Anda menginginkan indeks gabungan tetapi Anda tidak akan pernah bisa menggunakan kolom selanjutnya dalam Seek Predicates karena cara Anda menggunakan kolom sebelumnya, maka Anda mungkin juga memilikinya sebagai kolom yang disertakan, di mana mereka mengambil lebih sedikit ruang di index (karena mereka hanya disimpan pada level daun indeks, bukan level yang lebih tinggi) tetapi masih dapat menghindari pencarian dan digunakan dalam predikat residual.
Sesuai dengan istilah Residual Predicate - itu istilah saya sendiri untuk properti Seek ini. Gabung Gabung secara eksplisit menyebutnya setara dengan Predikat Residual, dan Pencocokan Hash menyebutnya sebagai Probe Residual (yang mungkin Anda dapatkan dari TSA jika Anda cocok untuk hash). Tetapi dalam Seek mereka menyebutnya Predikat yang membuatnya tampak lebih buruk daripada itu.
sumber
GetItemToProcessIndex tidak sepenuhnya dapat dicari karena di mana klausa Anda aktif
ItemState + LastAccessTime + CreationTime
. Kolom yang diindeks dan di mana klausa tidak cocok.Jika Anda membuat indeks penutup pada
ItemState + LastAccessTime + CreationTime
, untuk setiap pertandingan yang Anda dapatkan dari GetItemToProcessIndex, Anda juga mendapatkan nilai Kunci Utama Anda (ItemId). Itu hanya harus memastikan bahwa tanggal 2 adalah pertandingan.Ini yang Anda butuhkan untuk kemudian melompat ke lokasi baris pada halamannya dan memperbaruinya.
Dengan indeks Anda saat ini, ini mungkin membantu server untuk menemukan baris dengan ItemState yang Anda inginkan tetapi kemudian masih harus membaca semuanya dari indeks untuk menemukan kecocokan yang benar pada LastAccessTime + CreationTime. Bergantung pada predikat tanggal dan ukuran set yang cocok dan apa yang harus dikecualikan, ini dapat menghasilkan IO lebih banyak daripada indeks yang menutupi dengan sempurna pada 3 kolom saja yang akan mencari ItemState dan kolom kedua (tanggal diindeks 1) . Tanggal kedua dalam indeks bisa dimasukkan. Kolom tambahan tidak boleh diindeks di antara 3 ini meskipun bisa ok sebagai kolom ke-4 (lihat jawaban rob tentang kolom tambahan).
sumber