Mengekspresikan kueri menggunakan sintaks yang berbeda kadang-kadang dapat membantu mengomunikasikan keinginan Anda untuk menggunakan indeks yang tidak berkerumun ke pengoptimal. Anda harus menemukan formulir di bawah ini memberi Anda rencana yang Anda inginkan:
SELECT
[ID],
[DeviceID],
[IsPUp],
[IsWebUp],
[IsPingUp],
[DateEntered]
FROM [dbo].[Heartbeats]
WHERE
[ID] IN
(
-- Keys
SELECT TOP (1000)
[ID]
FROM [dbo].[Heartbeats]
WHERE
[DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
AND [DateEntered] < CONVERT(datetime, '2011-08-31', 121)
);
Bandingkan rencana itu dengan yang dihasilkan ketika indeks non-cluster dipaksa dengan petunjuk:
SELECT TOP (1000)
*
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE
[DateEntered] BETWEEN '2011-08-30' and '2011-08-31';
Rencana pada dasarnya sama (Pencarian Kunci tidak lebih dari pencarian pada indeks berkerumun). Kedua bentuk rencana hanya akan melakukan satu pencarian pada indeks non-clustered dan maksimum 1000 lookup ke indeks clustered.
Perbedaan yang penting adalah pada posisi operator Top. Diposisikan di antara dua pencarian, Top mencegah optimizer menggantikan dua operasi pencarian dengan pemindaian setara secara logis dari indeks berkerumun. Pengoptimal bekerja dengan mengganti bagian dari rencana logis dengan operasi relasional yang setara. Top bukan operator relasional, sehingga penulisan ulang mencegah transformasi ke pemindaian indeks berkerumun. Jika pengoptimal dapat memposisikan ulang operator Top, itu masih akan lebih suka pemindaian atas pencarian + pencarian karena cara estimasi biaya bekerja.
Biaya pemindaian dan pencarian
Pada tingkat yang sangat tinggi, model biaya pengoptimal untuk pemindaian dan pencarian cukup sederhana: ini memperkirakan bahwa 320 pencarian acak harganya sama dengan membaca 1350 halaman dalam pemindaian. Ini mungkin memiliki sedikit kemiripan dengan kemampuan perangkat keras dari setiap sistem I / O modern tertentu, tetapi itu bekerja dengan baik sebagai model praktis.
Model ini juga membuat sejumlah asumsi penyederhanaan, yang utama adalah bahwa setiap permintaan diasumsikan dimulai tanpa data atau halaman indeks yang sudah ada dalam cache. Implikasinya adalah bahwa setiap I / O akan menghasilkan I / O fisik - meskipun ini jarang terjadi dalam praktik. Bahkan dengan cache yang dingin, pre-fetching dan read-ahead berarti bahwa halaman-halaman yang dibutuhkan sebenarnya sangat mungkin berada di memori pada saat prosesor permintaan membutuhkannya.
Pertimbangan lain adalah bahwa permintaan pertama untuk baris yang tidak ada dalam memori akan menyebabkan seluruh halaman diambil dari disk. Permintaan baris berikutnya pada halaman yang sama kemungkinan besar tidak akan menimbulkan I / O fisik. Model penetapan biaya memang mengandung logika untuk memperhitungkan beberapa efek seperti ini, tetapi tidak sempurna.
Semua hal ini (dan lebih banyak lagi) berarti pengoptimal cenderung beralih ke pemindaian lebih awal daripada yang seharusnya. Acak I / O hanya 'jauh lebih mahal' daripada 'berurutan' I / O jika operasi fisik menghasilkan - mengakses halaman dalam memori memang sangat cepat. Bahkan di mana pembacaan fisik diperlukan, pemindaian mungkin tidak menghasilkan pembacaan berurutan sama sekali karena fragmentasi, dan berusaha dapat dilokasikan sedemikian rupa sehingga polanya pada dasarnya berurutan. Tambahkan ke bahwa karakteristik kinerja yang berubah dari sistem I / O modern (terutama solid-state) dan semuanya mulai terlihat sangat goyah.
Tujuan Baris
Kehadiran operator Top dalam rencana memodifikasi pendekatan penetapan biaya. Pengoptimal cukup pintar untuk mengetahui bahwa menemukan 1000 baris menggunakan pemindaian kemungkinan tidak akan memerlukan pemindaian seluruh indeks berkerumun - itu dapat berhenti segera setelah 1000 baris telah ditemukan. Ini menetapkan 'tujuan baris' dari 1000 baris di operator Top dan menggunakan informasi statistik untuk bekerja kembali dari sana untuk memperkirakan berapa banyak baris yang diharapkan dibutuhkan dari sumber baris (pemindaian dalam kasus ini). Saya menulis tentang rincian perhitungan ini di sini .
Gambar dalam jawaban ini dibuat menggunakan SQL Sentry Plan Explorer .