约束检查:TRY / CATCH vs Exists()

时间:2012-06-13 08:54:36

标签: sql-server sql-server-2008 sql-server-2005

我有一个对它有唯一约束的表:

create table dbo.MyTab
(
    MyTabID int primary key identity,
    SomeValue nvarchar(50)
);
Create Unique Index IX_UQ_SomeValue 
On dbo.MyTab(SomeValue);
Go

哪个代码最好检查重复项(如果找到重复项,则成功= 0)?

选项1

Declare @someValue nvarchar(50) = 'aaa'
Declare @success bit = 1;
Begin Try 
    Insert Into MyTab(SomeValue) Values ('aaa');
End Try
Begin Catch
    -- lets assume that only constraint errors can happen
    Set @success = 0;
End Catch
Select @success

选项2

Declare @someValue nvarchar(50) = 'aaa'
Declare @success bit = 1;
IF EXISTS (Select 1 From MyTab Where SomeValue = @someValue)
    Set @success = 0;
Else 
    Insert Into MyTab(SomeValue) Values ('aaa');
Select @success

从我的观点来看 - 我确实认为Try/Catch是错误的,不是预期的(例如死锁,甚至是不期望重复的约束)。在这种情况下,有时用户可能会尝试提交重复内容,因此会出现错误。

我发现article by Aaron Bertrand状态 - 即使大多数插入成功,检查重复项也不会太慢。

网上还有很多建议可以使用Try / Catch(避免2个语句不是1)。在我的环境中,可能只有1%的不成功案例,所以这种情况也是有道理的。

你有什么看法?使用选项1或选项2的其他原因是什么?

更新:在这种情况下我不确定它是否重要,但是表有而不是更新触发器(出于审计目的 - 行删除也通过Update语句发生)。

5 个答案:

答案 0 :(得分:4)

我看过这篇文章,但请注意,对于低故障率,我更喜欢“JFDI”模式。我之前在大容量系统上使用过这个(40k行/秒)。

在Aaron的代码中,在高负载和大量写入时首先测试时,您仍然可以获得重复。 (在dba.se上解释)这很重要:你的副本仍然会发生,而不是经常发生。您仍然需要异常处理并知道何时忽略重复错误(2627)

编辑:Remus在另一个答案中简洁地解释了

但是,我会有一个单独的TRY / CATCH来测试重复错误

BEGIN TRY

-- stuff

  BEGIN TRY
     INSERT etc
  END TRY
  BEGIN CATCH
      IF ERROR_NUMBER() <> 2627
        RAISERROR etc
  END CATCH

--more stuff

BEGIN CATCH
    RAISERROR etc
END CATCH

答案 1 :(得分:3)

首先,EXISTS(SELECT ...)是不正确的,因为它在并发下失败:多个事务可以同时运行检查 并且所有人都认为他们必须INSERT,一个将是幸运的首先插入的赢家,所有其余的将违反约束。换句话说,检查和插入之间存在竞争条件。所以你必须尝试 ,所以最好只试试/捕捉。

答案 2 :(得分:1)

记录错误

请不要这样做,但是在抛出异常时可能会出现日志记录。如果你在插入之前检查没有发生这样的事情。

知道何时以及何时可以破解

try / catch块应该用于可能因非确定性原因而中断的部分。我会说你在检查现有记录方面更明智,因为你知道它可能会破坏,为什么会这样。所以自己检查是从开发人员的角度来看更好的方法。

但是在你的代码中,它可能仍会在插入时中断,因为在检查时间和插入时间之间,其他一些用户已经将其插入...但这是(如前所述)非确定性错误。这就是你:

的原因
  1. 应该使用exists
  2. 进行核对
  3. try/catch
  4. 中插入

    自解释代码

    另一个好处是,从代码中可以看出为什么它可以破坏,而try / catch块可以隐藏它,并且可以删除它们认为为什么这里,它只是插入记录 ...

答案 3 :(得分:1)

选项 - 3

Begin Try
    SET XACT_ABORT ON
    Begin Tran
        IF NOT EXISTS (Select 1 From MyTab Where SomeValue = @someValue)
        Begin
            Insert Into MyTab(SomeValue) Values ('aaa');
        End
    Commit Tran
End Try

begin Catch
    Rollback Tran
End Catch

答案 4 :(得分:0)

为什么不在桌面上实现INSTEAD OF INSERT触发器?您可以检查该行是否存在,如果存在则不执行任何操作,如果不存在,则插入该行。