如何在SQL Server中创建多列唯一约束

29

我有一个表格,其中包含两个字段,我希望在数据库中使它们唯一。例如:

create table Subscriber (
    ID int not null,
    DataSetId int not null,
    Email nvarchar(100) not null,
    ...
)

ID列是主键,DataSetId和Email都被索引。

我想要做的是防止相同的Email和DataSetId组合出现在表中,换句话说,对于给定的DataSetId,Email值必须是唯一的。

我尝试在这些列上创建了一个唯一索引。

CREATE UNIQUE NONCLUSTERED INDEX IX_Subscriber_Email
ON Subscriber (DataSetId, Email)

但我发现这对搜索时间有相当大的影响(例如在搜索电子邮件地址时-表中有150万行)。

是否有更有效的方法来实现此类约束?


你是说没有索引的搜索比有索引的搜索要快得多吗?这对我来说是个新闻,因为据我所知,索引总是被创建来加速搜索,而不是减慢搜索速度。 - Germann Arlington
1
不,就这些内容,但它不应该对您的搜索时间产生任何显着影响!我们在谈论多大的影响?你可以展示执行计划吗?你是否更新了统计数据? - Lieven Keersmaekers
使用“简单”索引在电子邮件和DataSetId上搜索电子邮件地址大约需要1秒钟。通过添加额外的复合索引,这个时间增加到了约9秒钟。 - Neilski
我一整个早上都在研究这个问题,实际上我认为我有一个不同的问题......我看到的性能下降是从使用LINQ的MVC网页UI测量出来的(我知道,我知道)。确实,在添加索引时会显示显着的性能下降。如果我将LINQ中的SQL表达式直接在SQL Server Management Studio中运行,则实际上会看到性能提高(使用索引)。所以,很抱歉误导了大家。但我不明白的是,为什么从网页运行的LINQ表达式实际上要慢10倍。 - Neilski
2个回答

52

但我发现这对搜索时间有相当大的影响(例如,搜索电子邮件地址时)。

您定义的索引(DataSetId,Email)不能用于基于电子邮件的搜索。如果您将Email字段放在最左边的位置创建索引,则可以使用:

CREATE UNIQUE NONCLUSTERED INDEX IX_Subscriber_Email
   ON Subscriber (Email, DataSetId);

这个索引既可以作为唯一性约束的执行方式,也可以作为快速查找电子邮件的手段。但是这个索引不能用于快速搜索特定的 DataSetId

其要点在于每当定义多键索引时,它只能用于按键的顺序进行搜索。对于索引 (A,B,C),可以用于查找列 A 上的值,用于同时查找 AB 的值,或者用于查找所有三列 ABC 上的值。但是它不能被用于仅查找 BC 上的值。


1
所有的说法都是正确的,但这并不能解释为什么添加索引会对搜索时间产生(显著)负面影响。 - Lieven Keersmaekers
@Lieven:我怀疑OP不仅仅是创建了一个新的约束条件。例如,它可能会删除现有的“电子邮件”索引。 - Remus Rusanu
2
很可能但是用 艾尔默·富特 的话来说,OP 是“非常安静的” <g> - Lieven Keersmaekers
3
现在你只是在散布“FUD”(指故意制造恐惧、不确定性和怀疑的做法);) - Remus Rusanu
好的,我认为你指出了一个我不太理解的领域,也许你可以帮忙...目前DataSetID列和Email列都被索引,因为我需要根据DataSetId或/和电子邮件地址查找/过滤记录。从你的意思来看,我可以删除Email索引并替换为ON Subscriber(Email,DataSetId),但如果我“只是”想按DataSetId进行过滤,那么我不会有同样的性能问题吗? - Neilski
可以使用(Email)(Email, DataSetId)上的索引来搜索电子邮件,但第二个索引还可以强制执行唯一约束。同样,可以使用(DataSetId)(DataSetId, Email)上的索引来搜索DataSetId,但第二个索引可以强制执行唯一约束。此时最好提供其他信息,例如您创建的确切索引以及问题查询的执行计划。 - Remus Rusanu

-2

我假设只有通过存储过程才能将数据输入到该表中,如果是这种情况,您可以在插入和更新存储过程中实现一些逻辑,以查找要插入/更新的值是否已经存在于该表中。

类似于这样的操作:

create proc spInsert
(
    @DataSetId int,
    @Email nvarchar(100)
)
as
begin

if exists (select * from tabaleName where DataSetId = @DataSetId and Email = @Email)
    select -1 -- Duplicacy flag
else
begin
    -- insert logic here
    select 1 -- success flag
end

end
GO


create proc spUpdate
(
   @ID int,
   @DataSetId int,
   @Email nvarchar(100)
)
as
begin

if exists 
(select * from tabaleName where DataSetId = @DataSetId and Email = @Email and ID <> @ID)
    select -1 -- Duplicacy flag
else
begin
    -- insert logic here
    select 1 -- success flag
end

end
GO

-1 是建议使用过程式解决方案而不是约束执行的。此外,在并发情况下,该过程也无法正常工作。 - Remus Rusanu
@RemusRusanu “在并发下该过程将无法工作”你指的是什么意思? - yogi
1
多个事务可以同时检查条件并得出没有重复项的结论。所有线程都继续插入,从而创建了重复项。这是“检查和插入”编码模式中众所周知的问题:http://sqlblog.com/blogs/alexander_kuznetsov/archive/2010/01/12/t-sql-tuesday-002-patterns-that-do-not-work-as-expected.aspx - Remus Rusanu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接