Mengapa klausa WHERE saya mendapat manfaat dari kolom "termasuk"?

12

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 LastAccessTimemeskipun 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 WHEREmanfaat saya dari kolom yang disertakan atau membutuhkan kolom untuk menjadi bagian dari indeks?

sharptooth
sumber
Itu masih dapat mencari berdasarkan ItemStatenilai, tetapi pencarian tidak akan seefisien jika Indeks Anda disusun sebagai berikut(ItemState, CreationTime, LastAccessTime)
Mark Sinkinson
1
@MarkSinkinson atau hanya(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ
@sharptooth jawaban tertaut yang Anda miliki tidak mengatakan bahwa ("kecuali indeks dibangun di atas kolom yang digunakan untuk membatasi kueri tidak akan mendapat manfaat dari indeks"). Dikatakan bahwa indeks aktif (a,b)bukan yang terbaik untuk kueri SELECT a FROM t WHERE b=5;dan indeks aktif (b) INCLUDE (a)jauh lebih baik.
ypercubeᵀᴹ

Jawaban:

9

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.

Rob Farley
sumber
3

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).

Julien Vavasseur
sumber