Mengapa SQL Server mengabaikan indeks?

16

Saya punya tabel, CustPassMasterdengan 16 kolom di dalamnya, salah satunya adalah CustNum varchar(8), dan saya membuat indeks IX_dbo_CustPassMaster_CustNum. Ketika saya menjalankan SELECTpernyataan saya :

SELECT * FROM dbo.CustPassMaster WHERE CustNum = '12345678'

Itu mengabaikan indeks sepenuhnya. Ini membingungkan saya karena saya memiliki meja lain CustDataMasterdengan kolom lebih banyak (55), salah satunya adalah CustNum varchar(8). Saya membuat indeks pada kolom ini ( IX_dbo_CustDataMaster_CustNum) di tabel ini, dan menggunakan kueri yang sama:

SELECT * FROM dbo.CustDataMaster WHERE CustNum = '12345678'

Dan itu menggunakan indeks yang saya buat.

Apakah ada alasan khusus di balik ini? Mengapa menggunakan indeks dari CustDataMaster, tetapi bukan yang dari CustPassMaster? Apakah karena jumlah kolom yang rendah?

Kueri pertama mengembalikan 66 baris. Untuk yang kedua, 1 baris dikembalikan.

Juga, catatan tambahan: CustPassMastermemiliki 4991 catatan, dan CustDataMastermemiliki 5.376 catatan. Mungkinkah ini alasan di balik mengabaikan indeks? CustPassMasterjuga memiliki rekaman duplikat yang memiliki nilai yang sama CustNumjuga. Apakah ini faktor lain?

Saya mendasarkan klaim ini pada hasil rencana eksekusi aktual dari kedua pertanyaan.

Ini adalah DDL untuk CustPassMaster(yang memiliki indeks yang tidak digunakan):

CREATE TABLE dbo.CustPassMaster(
    [CustNum] [varchar](8) NOT NULL,
    [Username] [char](15) NOT NULL,
    [Password] [char](15) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustPassMaster_CustNum] ON dbo.CustPassMaster
(
    [CustNum] ASC
) WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Dan DDL untuk CustDataMaster(Saya telah menghilangkan banyak bidang yang tidak relevan):

CREATE TABLE dbo.CustDataMaster(
    [CustNum] [varchar](8) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustDataMaster_CustNum] ON dbo.CustDataMaster
(
    [CustNum] ASC
)WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Saya tidak memiliki indeks berkerumun di salah satu tabel tersebut, hanya satu indeks yang tidak tercakup.

Abaikan fakta bahwa tipe data tidak sepenuhnya cocok dengan tipe data yang disimpan. Bidang-bidang ini adalah cadangan dari basis data IBM AS / 400 DB2, dan ini adalah tipe data yang kompatibel untuknya. (Saya harus dapat menanyakan database cadangan ini dengan pertanyaan yang sama persis , dan mendapatkan hasil yang sama persis .)

Data ini hanya digunakan untuk SELECTpernyataan. Saya tidak melakukan pernyataan INSERT/ UPDATE/ DELETEdi atasnya, kecuali ketika aplikasi cadangan menyalin data dari AS / 400.

Der Kommissar
sumber
Mungkin patut membaca artikel ini tentang titik kritis dari NonClustered ke Clustered. sqlskills.com/blogs/kimberly/the-tipping-point-query-answers
Mark Sinkinson
3
Jadi itulah bedanya. Jika kueri pertama menggunakan indeks Anda, itu harus melakukan 65 pencarian. Ini mahal. Permintaan kedua hanya harus melakukan satu.
Aaron Bertrand

Jawaban:

18

Biasanya indeks akan digunakan oleh SQL Server jika dianggap lebih bijaksana untuk menggunakan indeks daripada langsung menggunakan tabel yang mendasarinya.

Tampaknya pengoptimal berbasis biaya berpikir akan lebih mahal untuk benar-benar menggunakan indeks yang dimaksud. Anda mungkin melihatnya menggunakan indeks jika alih-alih melakukan SELECT *, Anda cukup SELECT T1Col1.

Saat Anda SELECT *memberi tahu SQL Server untuk mengembalikan semua kolom dalam tabel. Untuk mengembalikan kolom-kolom itu SQL Server harus membaca halaman untuk baris yang cocok dengan WHEREkriteria pernyataan dari tabel itu sendiri (indeks berkerumun atau tumpukan). SQL Server mungkin berpikir jumlah pembacaan yang diperlukan untuk mendapatkan sisa kolom dari tabel berarti mungkin juga memindai tabel secara langsung. Akan bermanfaat untuk melihat permintaan aktual dan rencana eksekusi aktual yang digunakan oleh permintaan.

Max Vernon
sumber
3
Jadi solusi yang lebih jelas dan optimal bagi saya adalah membatasi kolom yang saya pilih, dan memasukkannya dalam INCLUDEklausa indeks?
Der Kommissar
1
Itu bisa membuat perbedaan besar. Menambahkan semua kolom yang dikembalikan oleh permintaan ke INCLUDEklausa kemungkinan akan membuat SQL Server menggunakan indeks. Karena itu, apa yang ingin Anda optimalkan? Sepertinya saya jika meja Anda memiliki ukuran baris rata-rata 100 byte, maka 5.000 baris hanya sekitar 500kb data, dan mungkin tidak layak menghabiskan waktu.
Max Vernon
1
Ukuran baris rata-rata adalah 0,30KB untuk Table1, dan 0,53KB untuk Table2. Semua data ini diimpor dari AS / 400 (IBM System i) dan tidak ada PK tentang apa pun. Saya secara manual membuat semua indeks hari ini setelah orang-orang menyebutkan bahwa aplikasi ini sangat lambat di kali.
Der Kommissar
10

Untuk menggunakan indeks, karena Anda lakukan select *, maka SQL Server harus terlebih dahulu membaca setiap baris dari indeks yang cocok dengan nilai yang Anda miliki di klausa mana. Berdasarkan ini, itu akan mendapatkan nilai indeks berkerumun untuk masing-masing baris, dan kemudian harus mencari masing-masing secara terpisah dari indeks berkerumun (= pencarian kunci). Karena Anda mengatakan bahwa nilainya tidak unik, SQL Server menggunakan statistik untuk memperkirakan berapa kali harus melakukan pencarian kunci ini.

Kemungkinan besar perkiraan biaya untuk pemindaian indeks kunci + pencarian non-cluster melebihi perkiraan biaya untuk scan indeks berkerumun, dan itulah sebabnya indeks diabaikan.

Anda bisa mencoba menggunakan set statistics io ondan kemudian menggunakan petunjuk indeks untuk melihat apakah biaya I / O sebenarnya lebih kecil saat menggunakan indeks atau tidak. Jika perbedaannya besar, Anda bisa melihat statistik, jika itu ketinggalan zaman.

Juga, jika SQL Anda benar-benar menggunakan variabel dan bukan nilai yang tepat, ini mungkin juga disebabkan oleh sniffing parameter (= nilai sebelumnya yang digunakan untuk membuat rencana memiliki banyak baris dalam tabel).

James Z
sumber
1

Itu mungkin alasannya. Pengoptimal berbasis biaya dan memutuskan jalur mana yang dipilih berdasarkan 'biaya' yang dimiliki masing-masing jalur eksekusi. Biaya 'terbesar' adalah mendapatkan data dari disk ke memori. Jika pengoptimal menghitung bahwa dibutuhkan lebih banyak waktu untuk membaca indeks dan data, maka mungkin memutuskan untuk melewati indeks. Semakin besar baris semakin banyak blok disk yang mereka ambil.

Marco
sumber