【问题标题】:Are SQL Server unique constraints "Silent" or do they raise an exception?SQL Server 唯一约束是“静默”还是引发异常?
【发布时间】:2020-09-19 20:15:02
【问题描述】:

我想防止在我的表中插入重复值。起初,我添加了代码来检查数据库中是否已经存在该值,但如果我可以在 DDL 级别阻止它,那似乎会产生很多开销/浪费时间。

所以我找到了this 并从此更改了我的一张表(例如):

CREATE TABLE [dbo].[ACTORS] 
(
    [Id]      INT IDENTITY (1, 1) NOT NULL,
    [ActorId] CHAR(9)     NOT NULL,
    [Actor]   VARCHAR(50) NOT NULL,

    PRIMARY KEY CLUSTERED ([Id] ASC),
);

到这里:

CREATE TABLE [dbo].[ACTORS] 
(
    [Id]      INT IDENTITY (1, 1) NOT NULL,
    [ActorId] CHAR(9)     NOT NULL,
    [Actor]   VARCHAR(50) NOT NULL,

    PRIMARY KEY CLUSTERED ([Id] ASC),

    CONSTRAINT [CK_ACTORS_Column] 
        UNIQUE NONCLUSTERED ([ActorId] ASC)
);

我希望约束阻止第二个相同的ActorId 而不会抱怨它。 IOW,绕过它,不要告诉我,不要停止应用程序或抛出异常。

这是它的工作方式(静默),还是会抛出异常?

【问题讨论】:

  • 使用create unique index dbfiddle.uk/… 时,您似乎可以使用IGNORE_DUP_KEY = ON 执行此操作
  • Unique Constraints and Check Constraints: "数据库引擎自动创建唯一索引来强制唯一性约束的唯一性要求。因此,如果尝试插入重复行,数据库引擎会返回错误声明 UNIQUE 约束已被违反且未将行添加到表中的消息。”

标签: sql sql-server tsql sql-insert unique-constraint


【解决方案1】:

@GMB 在他的回答中写道“SQL Server 没有内置选项(我知道)可以忽略此类错误”。

正如@a_horse_with_no_name 在评论中指出的那样,这里有一个相关的索引的IGNORE_DUP_KEY 选项。

你说:

我希望约束防止第二个相同的 ActorId 没有 抱怨[g]。 IOW,绕过它,不要告诉我, 不要停止应用程序或抛出异常。

可以通过这个选项实现。


首先,我应该指出,当您创建唯一约束时

CONSTRAINT [CK_ACTORS_Column] UNIQUE NONCLUSTERED ([ActorId] ASC)

引擎将创建一个唯一索引来强制执行约束。约束是逻辑概念,索引是概念的物理实现。

你可以通过只创建一个索引,一个唯一索引来达到同样的效果。创建索引时,您可以指定各种选项,包括 IGNORE_DUP_KEY 选项。

IGNORE_DUP_KEY = { 开 |关闭}

指定错误响应时 插入操作尝试将重复的键值插入到唯一的 指数。 IGNORE_DUP_KEY 选项仅适用于插入操作 在创建或重建索引之后。该选项在以下情况下无效 执行 CREATE INDEX、ALTER INDEX 或 UPDATE。默认为关闭。

ON 插入重复键值时会出现警告消息 成唯一索引。只有违反唯一性约束的行 会失败。

OFF 插入重复键值时会出现错误消息 成唯一索引。整个 INSERT 操作将被回滚。

默认情况下,此选项为OFF,因此尝试插入重复键值将失败并出现错误。服务器将回滚 INSERT 操作并将此错误消息发送到您的应用程序,这将取决于您的应用程序如何处理它。如果您的应用程序不期望它,它可能会抛出一些异常。

如果您将此选项设置为ON,您的应用程序将不再收到错误消息。它将收到一条警告消息,大多数应用程序通常会忽略该消息。因此,看起来服务器会默默地忽略重复值并仅插入那些不重复的值。

默默地忽略问题很少是一种理想的行为,但如果你真的知道自己在做什么,你就可以做到。


这是一个简短的演示。

让我们从你的桌子开始

CREATE TABLE [dbo].[ACTORS] 
(
    [Id]      INT IDENTITY (1, 1) NOT NULL,
    [ActorId] CHAR(9)     NOT NULL,
    [Actor]   VARCHAR(50) NOT NULL,

    PRIMARY KEY CLUSTERED ([Id] ASC),
);

选项 1。默认值。 IGNORE_DUP_KEY = OFF

CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
    [ActorId] ASC
) WITH (IGNORE_DUP_KEY = OFF)

桌子是空的。让我们尝试插入一些值。

insert into actors (actorid, actor) values('foo', 'bar');

-- (1 row affected)

让我们尝试插入一个重复的值:

insert into actors (actorid, actor) values('foo', 'baz');

--Msg 2601, Level 14, State 1, Line 4
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'. 
--The duplicate key value is (foo      ).
--The statement has been terminated.

让我们尝试在一条语句中插入多个重复值:

insert into actors (actorid, actor) values
('foo', 'baz'),
('fo2', 'baz'),
('fo2', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo3', 'baz'),
('fo4', 'baz');

--Msg 2601, Level 14, State 1, Line 16
--Cannot insert duplicate key row in object 'dbo.ACTORS' with unique index 'IX_ActorID'. 
--The duplicate key value is (foo      ).
--The statement has been terminated.

现在让我们看看表格中有什么。

SELECT * FROM Actors;

+----+-----------+-------+
| Id |  ActorId  | Actor |
+----+-----------+-------+
|  1 | foo       | bar   |
+----+-----------+-------+

只有第一个INSERT 语句成功并且只插入了一行。

现在,清理一下。

DROP INDEX [IX_ActorID] ON [dbo].[ACTORS];
TRUNCATE TABLE dbo.Actors;

选项 2。IGNORE_DUP_KEY = ON

CREATE UNIQUE NONCLUSTERED INDEX [IX_ActorID] ON [dbo].[ACTORS]
(
    [ActorId] ASC
) WITH (IGNORE_DUP_KEY = ON)

桌子是空的。让我们尝试插入一些值。

insert into actors (actorid, actor) values('foo', 'bar');

-- (1 row affected)

让我们尝试插入一个重复的值:

insert into actors (actorid, actor) values('foo', 'baz');

--Duplicate key was ignored.
--(0 rows affected)

如您所见,现在它不是错误消息。这只是一个警告“重复键被忽略。”

让我们尝试在一条语句中插入多个重复值:

insert into actors (actorid, actor) values
('foo', 'baz1'),
('fo2', 'baz2'),
('fo2', 'baz3'),
('fo3', 'baz4'),
('fo3', 'baz5'),
('fo3', 'baz6'),
('fo4', 'baz7');

--Duplicate key was ignored.
--(3 rows affected)

在这里您可以看到在 7 行中只插入了 3 行。

现在让我们看看表格中有什么。

SELECT * FROM Actors;

+----+-----------+-------+
| Id |  ActorId  | Actor |
+----+-----------+-------+
|  1 | foo       | bar   |
|  4 | fo2       | baz2  |
|  6 | fo3       | baz4  |
|  9 | fo4       | baz7  |
+----+-----------+-------+

您可以看到最后一个INSERT 语句插入了非重复值。另外,请仔细查看ID 列中的值。 IDENTITY 值有间隙,因为它们是为尝试插入的每一行生成的,并且其中一些行被唯一索引拒绝。


总体而言,此索引选项主要设计用于需要在单个 INSERT 语句中批量插入大量行的情况,但您希望其中 一些 行可能违反唯一性约束。您不希望整个大型 INSERT 语句失败,您只希望忽略少数违规行。如果没有这个选项,您将不得不尝试逐行插入值,这可能比单个 INSERT 语句慢得多。

【讨论】:

    【解决方案2】:

    让我们试试吧:

    insert into actors (actorid,actor) values('foo', 'bar');
    -- 1 row affected
    
    insert into actors (actorid, actor) values('foo', 'baz');
    -- Msg 2627 Level 14 State 1 Line 1
    -- Violation of UNIQUE KEY constraint 'CK_ACTORS_Column'. 
    -- Cannot insert duplicate key in object 'dbo.ACTORS'. The duplicate key value is (foo      ).
    

    违反唯一约束确实会引发错误。这就是数据库让您知道出现问题的方式。

    与许多其他数据库(MySQL、Postgres、SQLite...)不同,SQL Server 没有内置选项(据我所知)来忽略此类错误。一种解决方法是用not exists 和子查询重写insert

    insert into actors (actorid, actor)
    select v.*
    from (values ('foo', 'bar')) v(actorid, actor)
    where not exists (select 1 from actor a where a.actorid = v.actorid)
    

    另一个选项是merge 语句:

    merge into actors a
    using (values ('foo', 'bar')) v(actorid, actor)
    on v.actorid = a.actorid
    when not matched then insert (actorid, actor)
    values (v.actorid, v.actor)
    

    【讨论】:

    • 只是作为一个想法(不适用于生产!) - 要忽略错误,您可以将其包装在一个 TRY-CATCH 块中,其中没有任何内容,例如 BEGIN TRY insert into actors (actorid, actor) values('foo', 'baz'); END TRY BEGIN CATCH END CATCH。这不是 一个好的解决方案,因为任何错误都将被忽略,并且整个插入语句不会运行(如果插入多行,并且其中一个是无效的,那么所有的都将被丢弃)。最好通过 SQL 使用正确的逻辑(如 GMB 的答案)
    • @seanb, SQL Server has built-in no option to ignore such error 有一个名为IGNORE_DUP_KEY 的索引选项与此处相关。请看我的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多