このコードを少し分析してみましょう。
begin transaction
デフォルト設定を使用してトランザクションを開始しますREADCOMMITTED
。
select top 1 @number = NextNumber FROM settings
テーブルから最大の数を選択していSettings
ます (ちなみに、必ず句を追加するORDER BY
必要があります。そうしないと、順序が保証されません! ここで予期しない結果が生じる可能性があります)。
ただし、この操作はブロックされません - 2 つ以上のスレッドが同じ値、たとえば 100 を同時に読み取ることができます -SELECT
非常に短い時間だけ共有ロックを取得し、共有ロックは互換性があります - 複数のリーダーが価値を同時に。
Update Settings
set NextNumber = NextNumber + 1
ここで、1 つのスレッドが青信号を受け取り、新しい値 (この例では 101) をテーブルに書き戻します。テーブルには排他的なロックがありUPDATE
ます (後で排他ロックにエスカレートされます) - 同時に書き込みできるスレッドは 1 つだけです
UPDATE Data
set number = @nnumber, currentDate = GetDate(), IdUser = user_id(current_user)
FROM Data
INNER JOIN inserted on inserted.IdData = Data.IdData
同じこと-1つの幸運なスレッドがData
テーブルを更新し、番号を設定し、更新中の100
そのテーブルの行はトランザクションの終わりまでロックされます。
commit transaction
これで、ラッキー スレッドがトランザクションをコミットして完了です。
ただし、同じ元の値 100 を読み取った 2 番目 (および場合によっては 3 番目、4 番目、5 番目 .....) のスレッドは、まだ「ループ内」にあります。スレッド #1 が完了したので、これらのスレッドの 2 番目のスレッド自分のことをするようになります - それはします。Settings
テーブルを新しい値 102 に正しく更新し、変数に読み込んだ「現在の」値を使用して、テーブルの 2 回目の更新を続けData
ます....100
@number
最終的に、テーブルから同じ元の値 (100) をすべて読み取る複数のスレッドが存在する可能性があり、それらのそれぞれがテーブルを同じ「新しい」値 (101)Settings
に更新します。Settings
ここで使用しているこの方法は、負荷がかかると安全ではありません。
可能な解決策:
何よりもまず - これを行うための推奨される方法: テーブル内の列を使用して、データベースにこれ自体を処理させINT IDENTITY
ます (または、既に SQL Server 2012を使用している場合は、SEQUENCE
オブジェクトを使用してすべての同期を処理します)。
何らかの理由でこれを行うことができない場合は、少なくとも、ビジーなシステムでもコードが機能することを確認してください! たとえば、最初のスレッドが来て現在の値を読み取るときに、テーブルにSELECT .... WITH (UPDLOCK)
(排他的)UPDATE
ロックを設定するために使用する必要があります。これにより、最初のスレッドが完了するまで、他のすべてのスレッドが「現在の」値を読み取ることさえブロックされます。または、1 回の操作で古い値を更新して割り当てるなどの代替手段があります。Settigns
UPDATE