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、それは予期されていなかったエラーのためだと思います(デッドロックや、重複が予期されない場合の制約など)。この場合、ユーザーが重複して送信しようとする可能性があるため、エラーが予想されます。

ほとんどの挿入が成功した場合でも、重複のチェックはそれほど遅くないと述べているアーロン・バートランドの記事を見つけました。

ネット上には、Try/Catch を使用するためのアドバイスがたくさんあります (1 つではなく 2 つのステートメントを避けるため)。私の環境では、失敗するケースが 1% 程度ある可能性があるため、そのようなことも理にかなっています。

あなたの意見は何ですか?オプション 1 またはオプション 2 を使用する他の理由は何ですか?

更新: この場合、それが重要かどうかはわかりませんが、更新トリガーの代わりにテーブルがあります (監査目的で、行の削除も Update ステートメントを介して行われます)。

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 ...)同時実行で失敗するため、これは正しくありません。複数のトランザクションが同時にチェックを実行し、すべてが INSERT を実行する必要があると結論付けます。最初に挿入した幸運な勝者が 1 人になり、残りはすべて制約違反になります。つまり、チェックと挿入の間に競合状態があります。したがって、とにかくTRY/CATCH を実行する必要があるため、単に try/catch を実行することをお勧めします。

于 2012-06-13T09:11:30.740 に答える
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 に答える
1

エラーロギング

これについて私を拘束しないでください。ただし、例外がスローされたときにログに影響する可能性があります。挿入する前にチェックすると、そのようなことは起こりません。

壊れる理由と時期を知る

非決定的な理由で壊れる可能性がある部分には、try/catch ブロックを使用する必要があります。あなたのケースでは、既存のレコードが壊れる可能性があり、その理由が正確であることを知っているので、既存のレコードをチェックする方が賢明だと思います。したがって、自分でチェックすることは、開発者の観点から見るとより良い方法です。

しかし、あなたのコードでは、チェック時と挿入時の間に他のユーザーがすでに挿入しているため、挿入時に壊れる可能性があります...しかし、それは(前述のように)非決定論的エラーです。そのため、次のことを行います。

  1. で確認する必要がありますexists
  2. 中に挿入するtry/catch

自明のコード

もう 1 つの肯定的な点は、try/catch ブロックがそれを隠すことができるのにコードが壊れる理由がコードから明らかであることです。なぜこれがここにあるのかを考えてそれらを削除することができます。レコードを挿入しているだけです...

于 2012-06-13T09:02:28.753 に答える
0

INSTEAD OF INSERTテーブルにトリガーを実装してみませんか? 行が存在するかどうかを確認し、存在する場合は何もせず、存在しない場合は行を挿入できます。

于 2012-06-13T09:13:24.833 に答える