Indeks Kolom Terkomputasi Tidak Digunakan

14

Saya ingin memiliki pencarian cepat berdasarkan jika dua kolom sama. Saya mencoba menggunakan kolom yang dihitung dengan indeks, tetapi SQL Server tampaknya tidak menggunakannya. Jika saya hanya menggunakan kolom bit yang dihuni secara statis dengan indeks, saya mendapatkan pencarian indeks yang diharapkan.

Tampaknya ada beberapa pertanyaan lain seperti ini di luar sana, tetapi tidak ada yang berfokus pada mengapa indeks tidak akan digunakan.

Meja Uji:

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    )

create index ix_DiffPersisted on Diffs (DiffPersisted)
create index ix_DiffComp on Diffs (DiffComp)
create index ix_DiffStatic on Diffs (DiffStatic)

Dan Pertanyaannya:

select Id from Diffs where DiffPersisted = 1
select Id from Diffs where DiffComp = 1
select Id from Diffs where DiffStatic = 1

Dan rencana eksekusi yang dihasilkan: Rencana eksekusi

David Faivre
sumber

Jawaban:

10

Cobalah dengan COALESCEbukannya ISNULL. Dengan ISNULL, SQL Server tampaknya tidak mampu mendorong predikat terhadap indeks yang lebih sempit, dan karena itu harus memindai gugus untuk menemukan informasi.

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    );

Yang mengatakan, jika Anda tetap dengan kolom statis, indeks yang disaring mungkin lebih masuk akal, dan akan memiliki biaya I / O yang lebih rendah (semua tergantung pada berapa banyak baris yang biasanya cocok dengan predikat filter) misalnya:

CREATE INDEX ix_DiffStaticFiltered 
  ON dbo.Diffs(DiffStatic)
  WHERE DiffStatic = 1;
Aaron Bertrand
sumber
Sangat menarik, tidak akan memikirkan hal ini. Sepertinya Anda bisa menyingkirkannya COALESCEpada titik ini; Saya percaya CASEpernyataan sudah dijamin untuk kembali 0atau 1, tetapi ISNULLhanya hadir sehingga SQL Server akan menghasilkan non-nullable BITuntuk kolom yang dihitung. Namun, COALESCEmasih akan menghasilkan kolom yang dapat dibatalkan. Jadi satu dampak dari perubahan ini, dengan atau tanpa COALESCE, adalah bahwa kolom yang dikomputasi sekarang dapat dibatalkan tetapi pencarian indeks dapat digunakan.
Geoff Patterson
@ Geoff Ya, itu benar. Tapi dalam kasus ini karena kita tahu dengan computed NULL definisi kolom adalah benar-benar tidak output mungkin, ini hanya benar-benar penting jika kita menggunakan tabel ini sebagai sumber dari SELECT INTO.
Aaron Bertrand
Ini adalah beberapa informasi yang luar biasa - terima kasih! Tujuan akhir saya adalah agar kolom DataA dan DataB digunakan sebagai uuids "kotor" untuk memungkinkan pembaruan asinkron dari kolom yang didenormalkan pada catatan, jadi tidak boleh terlalu banyak di mana bendera Diff adalah 1. Jika saya menggunakan statis bidang, maka saya berpikir untuk menambahkan pemicu untuk memantau dua uuids dan memperbarui bidang.
David Faivre
Juga, seperti yang ditunjukkan @GeoffPatterson, tidak bisakah saya menggunakan COALESCE? Mengapa saya menyimpannya?
David Faivre
@ David Anda mungkin dapat menghapus COALESCE. Saya mencoba mempertahankan tampilan dan maksud kode asli Anda, dan tidak menguji tanpa itu, sehingga pengujian akan ada pada Anda. (Saya tidak bisa menjelaskan mengapa Anda harus ISNULLada di tempat pertama, baik.)
Aaron Bertrand
5

Ini adalah batasan spesifik dari logika pencocokan kolom SQL Server yang dikomputasi, saat terluar ISNULLdigunakan, dan tipe data kolom tersebut bit.

Laporan bug

Untuk menghindari masalah ini, salah satu dari solusi berikut dapat digunakan:

  1. Jangan gunakan terluar ISNULL(satu-satunya cara untuk membuat kolom yang dihitung NOT NULL).
  2. Jangan menggunakan bittipe data sebagai tipe terakhir dari kolom yang dihitung.
  3. Buat kolom yang dihitung PERSISTEDdan aktifkan tanda jejak 174 .

Detail

Inti dari masalah ini adalah bahwa tanpa jejak bendera 174, semua referensi kolom yang dihitung dalam kueri (bahkan bertahan) selalu diperluas ke definisi dasar sangat awal dalam kompilasi permintaan.

Gagasan ekspansi adalah bahwa hal itu dapat memungkinkan penyederhanaan dan penulisan ulang yang hanya dapat bekerja pada definisi, bukan pada nama kolom saja. Misalnya, mungkin ada predikat dalam kueri yang merujuk pada kolom yang dihitung yang dapat membuat bagian dari perhitungan menjadi berlebihan, atau lebih dibatasi.

Setelah penyederhanaan dan penulisan ulang awal dipertimbangkan, kompilasi permintaan mencoba untuk mencocokkan ekspresi dalam permintaan dengan kolom yang dihitung (semua kolom yang dihitung, tidak hanya yang awalnya ditemukan dalam teks permintaan).

Ekspresi kolom yang dihitung tidak berubah cocok dengan kolom yang dihitung sebelumnya tanpa masalah dalam kebanyakan kasus. Tampaknya ada bug ketika khusus untuk mencocokkan ekspresi bittipe, dengan terluar ISNULL. Pencocokan tidak berhasil dalam kasus khusus ini, bahkan di mana pemeriksaan terperinci dari internal menunjukkan bahwa itu harus berhasil.

Paul White 9
sumber