mencegah operator sisipan indeks berkerumun pada tampilan indeks yang tidak memenuhi syarat

8

Adakah yang tahu solusi untuk ini? Pada dasarnya, prosedur yang disimpan memaksa operator memasukkan terhadap tampilan yang diindeks, meskipun baris tidak memenuhi syarat. Akibatnya, ada kesalahan pemeran. Namun, untuk ad hocs, sql dengan benar menghilangkan tampilan dari pertimbangan.

Pertimbangkan skema berikut:

create table testdata (
    testid int identity(1,1) primary key
  , kind varchar(50)
  , data nvarchar(4000))
go
create view integer_testdata with schemabinding
as
select cast(a.data as int) data, a.kind, a.testid
  from dbo.testdata a
 where a.kind = 'integer'
go
create unique clustered index cl_intdata on integer_testdata(data)
go
create procedure insert_testdata
(
    @kind varchar(50)
  , @data nvarchar(4000)
)
as
begin
  insert into testdata (kind, data) values (@kind, @data)
end
go

Ini semua bekerja:

insert into testdata (kind, data) values ('integer', '1234');
insert into testdata (kind, data) values ('integer', 12345);
insert into testdata (kind, data) values ('noninteger', 'noninteger');
exec insert_testdata @kind = 'integer', @data = '123456';
exec insert_testdata @kind = 'integer', @data = 1234567;

Ini gagal:

exec insert_testdata @kind = 'noninteger', @data = 'noninteger';

Perbandingan "perkiraan rencana eksekusi":

insert into testdata (kind, data) values ('noninteger', 'noninteger'): masukkan deskripsi gambar di sini

exec insert_testdata @kind = 'noninteger', @data = 'noninteger': masukkan deskripsi gambar di sini

cocogorilla
sumber
Adakah perbedaan penting antara rencana pengadaan ad hoc dan cache yang disimpan per kesempatan?
Ali Razeghi
ya, ketika Anda menjalankan ad hoc, Anda tidak mendapatkan operator terhadap tampilan yang diindeks ... Saya pikir sql cukup pintar untuk melihat ada filter pada tampilan dan menghilangkannya dari pertimbangan (penghapusan ini tidak terjadi dalam proc)
cocogorilla
4
Tidak dalam posisi untuk menguji tetapi apakah menambah option (recompile)bantuan?
Martin Smith
2
Hanya karena penasaran, masalah apa yang Anda coba selesaikan. Baunya seperti masalah XY .
Max Vernon
1
@ MaxVernon Saya sedang bekerja dengan struktur data yang ada dan membutuhkan pencarian cepat pada nilai integer unik yang disimpan dalam subset nvarchar (4000), filter pada kolom lain mendefinisikan subset baris.
cocogorilla

Jawaban:

6

Terima kasih telah menyediakan skrip lengkap untuk menciptakan kembali masalah.

Saya mengujinya dengan SQL Server 2014 Express.

Ketika saya menambahkannya OPTION(RECOMPILE)berfungsi:

ALTER procedure [dbo].[insert_testdata]
(
    @kind varchar(50)
  , @data nvarchar(4000)
)
as
begin
  insert into testdata (kind, data) 
  values (@kind, @data)
  OPTION(RECOMPILE);
end

Ketika saya menjalankan ini di SSMS:

exec insert_testdata @kind = 'noninteger', @data = 'noninteger';

Saya menerima pesan ini:

(1 row(s) affected)

dan satu baris ditambahkan ke tabel.

Apa versi SQL Server yang Anda gunakan? Samar-samar saya ingat bahwa dalam versi sebelum 2008 ini OPTION(RECOMPILE)berperilaku sedikit berbeda.


Saya sedang bekerja dengan struktur data yang ada dan membutuhkan pencarian cepat pada nilai integer unik yang disimpan dalam subset nvarchar (4000), filter pada kolom lain mendefinisikan subset baris.

Dalam hal ini mungkin lebih baik menggunakan indeks yang disaring daripada tampilan yang diindeks:

CREATE UNIQUE NONCLUSTERED INDEX [IX_DataFiltered] ON [dbo].[testdata]
(
    [data] ASC
)
WHERE ([kind]='integer')

Pengoptimal harus menggunakan indeks ini ketika WHEREfilter kueri sama persis dengan WHEREklausa indeks.

Ya, indeks di sini ada pada nvarcharkolom yang mungkin bukan yang terbaik, terutama jika Anda bergabung dengan tabel ini dengan intkolom dari tabel lain, atau mencoba memfilter nilai dalam kolom ini menggunakan intnilai.


Varian lain yang terlintas dalam pikiran adalah kolom komputasi tetap yang dikonversi nvarcharmenjadi int. Pada dasarnya itu sangat mirip dengan pandangan Anda, tetapi nvarcharnilai-nilai tetap yang dikonversi menjadi intdisimpan dengan tabel yang sama, bukan dalam objek yang terpisah.

CREATE TABLE [dbo].[testdata](
    [testid] [int] IDENTITY(1,1) NOT NULL,
    [kind] [varchar](50) NULL,
    [data] [nvarchar](4000) NULL,
    [int_data]  AS (case when [kind]='integer' then CONVERT([int],[data]) end) PERSISTED,
PRIMARY KEY CLUSTERED 
(
    [testid] ASC
))


CREATE UNIQUE NONCLUSTERED INDEX [IX_int_data_filtered] ON [dbo].[testdata]
(
    [int_data] ASC
)
WHERE ([kind]='integer')

Dengan pengaturan ini saya mencoba menggunakan prosedur tersimpan asli Anda untuk menyisipkan baris dan itu berhasil bahkan tanpa OPTION(RECOMPILE).


Sebenarnya, sepertinya alasan utama mengapa kolom bertahan di atas berfungsi adalah yang saya gunakan CASE. Jika saya menambah CASEdefinisi tampilan Anda, prosedur tersimpan berfungsi tanpa OPTION(RECOMPILE).

create view integer_testdata2 with schemabinding
as
select 
    case when a.kind='integer' then CONVERT(int, a.data) end as data
    , a.kind, a.testid
from dbo.testdata a
where a.kind = 'integer'
go
Vladimir Baranov
sumber
Saya tidak yakin indeks yang difilter akan berfungsi dengan baik karena lebar kolom 4000 (jauh di atas batas 900). Saya tidak pernah berpikir untuk menggunakan opsi petunjuk petunjuk kompilasi ulang ... Saya menerapkan kompilasi ulang untuk seluruh prosedur. Saran Anda bekerja untuk semua kasus pengujian saya! Terima kasih.
cocogorilla
1
Ya, indeks yang difilter pada kolom asli mungkin tidak terlalu berguna. Saya menambahkan varian lain dengan kolom yang dihitung tetap.
Vladimir Baranov
Saya suka opsi kolom yang dihitung terus-menerus ... yang menurut saya solusi yang tepat
cocogorilla