8

我有一个具有唯一约束的表:

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是因为错误,这是不期望的(比如死锁,甚至在不期望重复时的约束)。在这种情况下 - 有时用户可能会尝试提交副本,因此会出现错误。

我发现Aaron Bertrand 的文章指出,即使大多数插入成功,检查重复项也不会慢很多。

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

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

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

4

5 回答 5

5

我看过那篇文章,但请注意,对于低失败率,我更喜欢“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
于 2012-06-13T09:03:53.427 回答
3

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

于 2012-06-13T09:11:30.740 回答
1

错误记录

不要因为这个而抓住我,但是当抛出异常时可能会有日志记录的影响。如果您在插入之前检查,则不会发生这种情况。

知道为什么和什么时候它可以打破

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

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

  1. 应该检查exists
  2. 插入try/catch

不言自明的代码

另一个积极的方面是,从代码中可以清楚地看到为什么它会中断,而 try/catch 块可以隐藏它,人们可能会认为为什么会在这里,它只是插入记录......

于 2012-06-13T09:02:28.753 回答
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
于 2012-06-14T18:25:03.470 回答
0

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

于 2012-06-13T09:13:24.833 回答