防止在不合格的索引视图上聚集索引插入运算符


8

有谁知道解决方法?从本质上讲,即使行不符合条件,存储过程也会强制对索引视图执行插入操作。结果,存在转换错误。但是,对于临时而言,sql可以正确地消除视图的考虑。

考虑以下架构:

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

所有这些工作:

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;

这将失败:

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

“估计执行计划”的比较:

insert into testdata (kind, data) values ('noninteger', 'noninteger')在此处输入图片说明

exec insert_testdata @kind = 'noninteger', @data = 'noninteger'在此处输入图片说明


每次机会的临时计划和缓存的存储过程计划之间是否有显着差异?
阿里·拉泽吉

是的,当您临时执行时,您不会对索引视图执行任何运算符...我认为sql很聪明,可以看到视图上有一个过滤器并将其从考虑中删除(这种消除不会在proc)
cocogorilla

4
无法测试,但是可以添加option (recompile)帮助吗?
马丁·史密斯

2
出于好奇,您想解决什么问题。这闻起来像XY问题
Max Vernon 2015年

1
@MaxVernon我正在使用现有的数据结构,需要快速查找存储在nvarchar(4000)子集中的唯一整数值​​,另一列上的过滤器定义了该行的子集。
cocogorilla

Answers:


6

感谢您提供完整的脚本来重新创建问题。

我使用SQL Server 2014 Express进行了测试。

当我添加OPTION(RECOMPILE)它时:

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

当我在SSMS中运行此命令时:

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

我收到此消息:

(1 row(s) affected)

并将一行添加到表中。

您正在使用哪个版本的SQL Server?我隐约记得在2008年之前的版本中,此OPTION(RECOMPILE)行为有些不同。


我正在使用现有的数据结构,需要快速查找存储在nvarchar(4000)子集中的唯一整数值​​,另一列上的过滤器定义了该行子集。

在这种情况下,最好使用过滤索引而不是索引视图:

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

WHERE查询的过滤器与WHERE索引的子句完全匹配时,优化器应使用该索引。

是的,这里的索引位于nvarchar列上可能不是最好的选择,特别是如果您将此表与int另一个表的列连接在一起,或者尝试使用int值来过滤此列中的值。


我想到的另一个变体是持久化计算列,该转换nvarcharint。从本质上讲,它与您的视图非常相似,但是nvarchar转换成的持久化值int存储在同一表中,而不是存储在单独的对象中。

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')

通过此设置,我尝试使用您的原始存储过程插入行,即使没有也可以使用OPTION(RECOMPILE)


实际上,似乎上述持久化列起作用的主要原因是我使用CASE。如果添加CASE您的视图定义,则存储过程可以不使用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

我不确定过滤后的索引是否会正常工作,因为列宽为4000(远高于限制900)。我没有想到要使用查询提示选项重新编译...我正在对整个过程应用重新编译。您的建议适用于我所有的测试用例!谢谢。
cocogorilla

1
是的,原始列上的筛选索引可能不是很有用。我添加了另一个具有持久化计算列的变量。
弗拉基米尔·巴拉诺夫

我喜欢持久的计算列选项……这使我成为正确的解决方案
cocogorilla
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.