Indeks yang difilter sangat kuat, tetapi saya masih melihat beberapa kebingungan di luar sana tentang indeks tersebut – terutama tentang kolom yang digunakan dalam filter, dan apa yang terjadi jika Anda ingin mengencangkan filter.
Sebuah pertanyaan baru-baru ini di dba.stackexchange meminta bantuan tentang mengapa kolom yang digunakan dalam filter dari indeks yang difilter harus dimasukkan dalam kolom 'disertakan' dari indeks. Pertanyaan yang bagus – kecuali bahwa saya merasa ini dimulai dengan premis yang buruk, karena kolom tersebut tidak harus disertakan dalam indeks . Ya, mereka membantu, tetapi tidak seperti yang ditunjukkan oleh pertanyaan itu.
Agar Anda tidak melihat pertanyaan itu sendiri, berikut ringkasan singkatnya:
Untuk memenuhi permintaan ini…
SELECT Id, DisplayName FROM Users WHERE Reputation > 400000;
…indeks yang difilter berikut ini cukup bagus:
CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club ON dbo.Users ( DisplayName, Id ) INCLUDE ( Reputation ) WHERE Reputation > 400000;
Namun meskipun memiliki indeks ini, Pengoptimal Kueri merekomendasikan indeks berikut jika nilai yang difilter diperketat menjadi, katakanlah, 450000.
CREATE NONCLUSTERED INDEX IndexThatWasMissing ON dbo.Users ( Reputation ) INCLUDE ( DisplayName, Id );
Saya memparafrasekan pertanyaannya sedikit di sini, yang dimulai dengan merujuk pada situasi ini dan kemudian membangun contoh yang berbeda, tetapi idenya sama. Saya hanya tidak ingin membuat segalanya menjadi lebih rumit dengan melibatkan tabel terpisah.
Intinya adalah – indeks yang disarankan oleh QO adalah indeks asli tetapi diputarbalikkan. Indeks asli memiliki Reputasi dalam daftar INCLUDE, dan DisplayName dan Id sebagai kolom kunci, sedangkan indeks baru yang direkomendasikan adalah kebalikannya dengan Reputasi sebagai kolom kunci dan DisplayName &ID di INCLUDE. Mari kita lihat alasannya.
Pertanyaan tersebut mengacu pada posting oleh Erik Darling, di mana dia menjelaskan bahwa dia menyetel kueri '450.000' di atas dengan memasukkan Reputasi ke dalam kolom TERMASUK. Erik menunjukkan bahwa tanpa Reputasi dalam daftar TERMASUK, kueri yang memfilter ke nilai Reputasi yang lebih tinggi perlu melakukan Pencarian (buruk!), atau bahkan mungkin menyerah sepenuhnya pada indeks yang difilter (berpotensi lebih buruk). Dia menyimpulkan bahwa memiliki kolom Reputasi dalam daftar INCLUDE memungkinkan SQL memiliki statistik, sehingga dapat membuat pilihan yang lebih baik, dan menunjukkan bahwa dengan Reputasi di INCLUDE berbagai kueri bahwa semua memfilter pada nilai Reputasi yang lebih tinggi semua memindai indeks yang difilter.
Dalam jawaban atas pertanyaan dba.stackexchange, Brent Ozar menunjukkan bahwa peningkatan Erik tidak terlalu bagus karena menyebabkan Pemindaian. Saya akan kembali ke yang itu, karena itu poin yang menarik, dan agak salah.
Pertama, mari kita berpikir sedikit tentang indeks secara umum.
Sebuah indeks menyediakan struktur yang dipesan untuk satu set data. (Saya bisa bertele-tele dan menunjukkan bahwa membaca data dalam indeks dari awal hingga akhir mungkin membuat Anda melompat dari halaman ke halaman dengan cara yang tampak serampangan, tetapi tetap saja saat Anda membaca halaman, mengikuti petunjuk dari satu halaman ke halaman berikutnya Anda dapat yakin bahwa data tersebut diurutkan. Dalam setiap halaman Anda bahkan mungkin melompat-lompat untuk membaca data secara berurutan, tetapi ada daftar yang menunjukkan bagian (slot) halaman mana yang harus dibaca dalam urutan mana. Benar-benar ada tidak ada gunanya kesombongan saya kecuali untuk menjawab mereka yang sama-sama bertele-tele yang akan berkomentar jika saya tidak.)
Dan urutan ini sesuai dengan kolom kunci – itu adalah bagian mudah yang didapat semua orang. Ini berguna tidak hanya karena dapat menghindari pengurutan ulang data nanti, tetapi juga untuk dapat dengan cepat menemukan baris atau rentang baris tertentu menurut kolom tersebut.
Tingkat daun indeks berisi nilai dalam kolom mana pun dalam daftar TERMASUK, atau dalam kasus Indeks Tergugus, nilai di semua kolom dalam tabel (kecuali kolom terkomputasi yang tidak bertahan). Level lain dalam indeks hanya berisi kolom kunci dan (jika indeks tidak unik) alamat unik baris – yang merupakan salah satu kunci indeks berkerumun (dengan uniquifier baris jika indeks berkerumun juga tidak unik ) atau nilai RowID untuk heap, cukup untuk memungkinkan akses mudah ke semua nilai kolom lainnya untuk baris. Level daun juga mencakup semua informasi 'alamat'.
Tapi bukan itu yang menarik dari postingan kali ini. Bagian yang menarik untuk posting ini adalah apa yang saya maksud dengan "ke satu set data". Ingat saya mengatakan "Indeks menyediakan struktur terurut ke kumpulan data ".
Dalam indeks berkerumun, kumpulan data itu adalah seluruh tabel, tetapi bisa juga sesuatu yang lain. Anda mungkin sudah dapat membayangkan bagaimana sebagian besar indeks yang tidak berkerumun tidak melibatkan semua kolom tabel. Ini adalah salah satu hal yang membuat indeks non-cluster sangat berguna, karena biasanya jauh lebih kecil daripada tabel yang mendasarinya.
Dalam kasus tampilan yang diindeks, kumpulan data kami dapat berupa hasil dari keseluruhan kueri, termasuk gabungan di banyak tabel! Itu untuk posting lain.
Namun dalam indeks yang difilter, ini bukan hanya salinan subset kolom, tetapi juga subset baris. Jadi dalam contoh di sini, indeks hanya untuk pengguna dengan reputasi lebih dari 400 ribu.
CREATE UNIQUE NONCLUSTERED INDEX Users_400k_Club_NoInclude ON dbo.Users ( DisplayName, Id ) WHERE Reputation > 400000;
Indeks ini mengambil pengguna yang memiliki reputasi lebih dari 400 ribu, dan mengurutkannya berdasarkan DisplayName dan Id. Bisa unik karena (asumsi) kolom Id sudah unik. Jika Anda mencoba sesuatu yang serupa di meja Anda sendiri, Anda mungkin perlu berhati-hati.
Tapi pada titik ini, indeks tidak peduli apa Reputasi untuk setiap pengguna – hanya peduli apakah Reputasi cukup tinggi untuk berada di indeks atau tidak. Jika reputasi pengguna diperbarui dan melampaui ambang batas, maka DisplayName dan Id pengguna akan dimasukkan ke dalam indeks. Jika turun di bawah, itu akan dihapus dari index. Ini seperti memiliki meja terpisah untuk para pemain tinggi, kecuali bahwa kami memasukkan orang ke dalam tabel itu dengan meningkatkan nilai Reputasi mereka di atas ambang 400k di tabel yang mendasarinya. Itu dapat melakukan ini tanpa harus benar-benar menyimpan nilai Reputasi itu sendiri.
Jadi sekarang jika kita ingin menemukan orang yang memiliki ambang di atas 450k, indeks tersebut kehilangan beberapa informasi.
Tentu, kami dapat dengan yakin mengatakan bahwa setiap orang yang akan kami temukan ada dalam indeks itu – tetapi indeks itu sendiri tidak berisi informasi yang cukup untuk menyaring lebih jauh tentang Reputasi. Jika saya memberi tahu Anda, saya memiliki daftar abjad film pemenang Oscar Gambar Terbaik dari tahun 1990-an (American Beauty, Braveheart, Dances With Wolves, English Patient, Forrest Gump, Schindler's List, Shakespeare in Love, Silence of the Lambs, Titanic, Unforgiven) , maka saya dapat meyakinkan Anda bahwa pemenang untuk 1994-1996 akan menjadi bagian dari itu, tetapi saya tidak dapat menjawab pertanyaan tanpa terlebih dahulu mendapatkan beberapa informasi lebih lanjut.
Jelas indeks saya yang difilter akan lebih berguna jika saya memasukkan tahun, dan bahkan berpotensi lebih jika tahun adalah kolom kunci, karena kueri baru saya ingin menemukan yang untuk 1994-1996. Tapi saya mungkin merancang indeks ini di sekitar kueri untuk membuat daftar semua film dari tahun 1990-an dalam urutan abjad. Permintaan itu tidak peduli tentang tahun sebenarnya, hanya apakah itu tahun 1990-an atau tidak, dan saya bahkan tidak perlu mengembalikan tahun – hanya judulnya – jadi saya dapat memindai indeks yang difilter untuk mendapatkan hasilnya. Untuk kueri itu, saya bahkan tidak perlu menyusun ulang hasil atau menemukan titik awal – indeks saya benar-benar sempurna.
Contoh yang lebih praktis untuk tidak memperdulikan nilai kolom pada filter adalah pada status, seperti:
WHERE IsActive = 1
Saya sering melihat kode yang memindahkan data dari satu tabel ke tabel lainnya saat baris berhenti 'aktif'. Orang-orang tidak ingin baris lama mengacaukan tabel mereka, dan mereka menyadari bahwa data 'panas' mereka hanyalah sebagian kecil dari semua data mereka. Jadi mereka memindahkan data pendinginan ke tabel Arsip, menjaga tabel Aktif tetap kecil.
Indeks yang difilter dapat melakukan ini untuk Anda. Di balik layar. Segera setelah Anda memperbarui baris dan mengubah kolom IsActive menjadi sesuatu selain 1. Jika Anda hanya peduli memiliki data aktif di sebagian besar indeks Anda, maka indeks yang difilter adalah ideal. Itu bahkan akan mengembalikan baris ke indeks jika nilai IsActive berubah kembali ke 1.
Tetapi Anda tidak perlu memasukkan IsActive ke dalam daftar INCLUDE untuk mencapai ini. Mengapa Anda ingin menyimpan nilainya – Anda sudah tahu apa nilainya – itu 1! Kecuali jika Anda meminta untuk mengembalikan nilai, Anda seharusnya tidak membutuhkannya. Dan mengapa Anda mengembalikan nilai ketika Anda sudah tahu bahwa jawabannya adalah 1, kan?! Kecuali itu dengan putus asa, statistik yang dimaksud Erik di posnya akan memanfaatkan berada dalam daftar TERMASUK. Anda tidak memerlukannya untuk kueri, tetapi Anda harus memasukkannya untuk statistik.
Mari kita pikirkan tentang apa yang perlu dilakukan Pengoptimal Kueri untuk mengetahui kegunaan indeks.
Sebelum dapat berbuat banyak, ia perlu mempertimbangkan apakah indeks tersebut merupakan kandidat. Tidak ada gunanya menggunakan indeks jika tidak memiliki semua baris yang mungkin diperlukan – kecuali jika kita memiliki cara yang efektif untuk mendapatkan sisanya. Jika saya menginginkan film dari tahun 1985-1995, maka indeks film tahun 1990-an saya tidak ada gunanya. Tapi untuk 1994-1996, mungkin tidak buruk.
Pada titik ini, seperti pertimbangan indeks lainnya, saya perlu memikirkan apakah itu akan cukup membantu untuk menemukan data dan memasukkannya ke dalam urutan yang akan membantu mengeksekusi sisa kueri (mungkin untuk Gabung Gabung, Agregat Aliran, memuaskan ORDER BY, atau berbagai alasan lainnya). Jika filter kueri saya sama persis dengan filter indeks, maka saya tidak perlu memfilter lebih jauh – cukup menggunakan indeks saja. Kedengarannya bagus, tetapi jika tidak sama persis, jika filter kueri saya lebih ketat dari filter indeks (seperti contoh 1994-1996 saya, atau 450.000 Erik), saya perlu memiliki nilai Tahun atau nilai Reputasi untuk memeriksa – semoga mendapatkannya dari INCLUDEd di level daun atau di suatu tempat di kolom kunci saya. Jika mereka tidak ada dalam indeks, saya harus melakukan Pencarian untuk setiap baris dalam indeks saya yang difilter (dan idealnya, memiliki gagasan tentang berapa kali Pencarian saya akan dipanggil, yang merupakan statistik yang diinginkan Erik kolom yang disertakan untuk).
Idealnya, indeks apa pun yang saya rencanakan untuk digunakan diurutkan dengan benar (melalui kunci), TERMASUK semua kolom yang harus saya kembalikan, dan sudah difilter ke baris yang saya butuhkan. Itu akan menjadi indeks yang sempurna, dan rencana eksekusi saya adalah Pemindaian.
Itu benar, sebuah SCAN. Bukan Pencarian, tapi Pemindaian. Itu akan dimulai pada halaman pertama indeks saya dan terus memberi saya baris sampai saya mendapatkan sebanyak yang saya butuhkan, atau sampai tidak ada lagi baris untuk dikembalikan. Tidak melewatkan apa pun, tidak menyortirnya – hanya memberi saya baris secara berurutan.
A Seek akan menyarankan bahwa saya tidak memerlukan seluruh indeks, yang berarti saya membuang-buang sumber daya dalam mempertahankan bagian indeks itu, dan untuk menanyakannya saya harus menemukan titik awal dan terus memeriksa baris untuk melihat apakah saya sudah mencapai akhir atau tidak. Jika Pemindaian saya memiliki Predikat, maka tentu saja, saya harus melihat (dan menguji) lebih banyak data daripada yang saya perlukan, tetapi jika filter indeks saya sempurna, maka Pengoptimal Kueri harus mengenalinya dan tidak harus melakukan pemeriksaan tersebut .
Pemikiran Terakhir
TERMASUK tidak penting untuk indeks yang difilter. Mereka berguna untuk menyediakan akses mudah ke kolom yang mungkin berguna untuk kueri Anda, dan jika Anda memperketat apa yang ada di indeks yang difilter dengan kolom mana pun, apakah itu disebutkan dalam filter atau tidak, Anda harus mempertimbangkan untuk memasukkan kolom itu campuran. Tetapi pada saat itu Anda harus bertanya apakah filter indeks Anda adalah filter yang benar, apa lagi yang harus Anda miliki dalam daftar INCLUDE Anda, dan bahkan kolom kunci apa yang seharusnya. Permintaan Erik tidak berjalan dengan baik karena dia membutuhkan informasi yang tidak ada dalam indeks, meskipun dia telah menyebutkan kolom di filter. Dia menemukan penggunaan yang baik untuk statistik juga, dan saya masih akan mendorong Anda untuk memasukkan kolom filter karena alasan itu. Tetapi memasukkannya ke dalam INCLUDE tidak memungkinkan mereka untuk tiba-tiba mulai melakukan Seek, karena bukan itu cara kerja indeks, baik difilter atau tidak.
Saya ingin Anda, pembaca, memahami indeks yang difilter dengan sangat baik. Mereka sangat berguna dan, ketika Anda mulai membayangkannya seperti tabel dengan haknya sendiri, dapat menjadi bagian dari keseluruhan desain database Anda. Mereka juga merupakan alasan untuk selalu menggunakan setelan ANSI_NULLs dan QUOTED_IDENTIFIER, karena Anda akan mendapatkan kesalahan dari indeks yang difilter kecuali setelan tersebut AKTIF, tetapi mudah-mudahan Anda tetap memastikan setelan tersebut selalu aktif.
Oh, dan film-film itu adalah Forrest Gump, Braveheart, dan The English Patient.
@rob_farley