Read Committed Snapshot は、テーブルからデータを選択する際のロックのみを処理します。
ただし、t1 と t2 では、別のシナリオであるデータを更新しています。
カウンターを更新すると、(行で) 書き込みロックにエスカレートし、他の更新が発生しなくなります。t2 は読み取ることができますが、t2 は t1 が完了するまで UPDATE をブロックし、t2 は t1 の前にコミットできません (タイムラインに反します)。カウンターを更新できるトランザクションは 1 つだけなので、提示されたコードがあれば、両方のトランザクションでカウンターが正しく更新されます。(テスト済み)
- カウンター = 0
- t1 更新カウンター (カウンター => 1)
- t2 更新カウンター (ブロック)
- t1 コミット (カウンター = 1)
- t2 のブロックが解除されました (カウンターを更新できるようになりました) (カウンター => 2)
- t2コミット
Read Committed は、コミットされた値のみを読み取ることができることを意味しますが、反復可能な読み取りがあることを意味するわけではありません。したがって、カウンター変数を使用および依存し、後で更新する場合は、トランザクションを間違った分離レベルで実行している可能性があります。
反復可能な読み取りロックを使用するか、カウンターをたまにしか更新しない場合は、楽観的ロック手法を使用して自分で行うことができます。たとえば、カウンタ テーブルを含むタイムスタンプ列、または条件付き更新などです。
DECLARE @CounterInitialValue INT
DECLARE @NewCounterValue INT
SELECT @CounterInitialValue = SELECT counter FROM MyTable WHERE MyID = 1234
-- do stuff with the counter value
UPDATE MyTable
SET counter = counter + 1
WHERE
MyID = 1234
AND
counter = @CounterInitialValue -- prevents the update if counter changed.
-- the value of counter must not change in this scenario.
-- so we rollback if the update affected no rows
IF( @@ROWCOUNT = 0 )
ROLLBACK
このdevx記事は参考情報ですが、まだベータ版であった機能について説明しているため、完全に正確ではない可能性があります。
更新: Justice が示すように、t2 が t1 のネストされたトランザクションである場合、セマンティクスは異なります。繰り返しますが、どちらもカウンターを正しく更新します (+2)。これは、t1 内の t2 の観点から、カウンターが既に 1 回更新されているためです。ネストされた t2 は、t1 が更新する前のカウンターにアクセスできません。
- カウンター = 0
- t1 更新カウンター (カウンター => 1)
- t2 更新カウンター (ネストされたトランザクション) (カウンター => 2)
- t2コミット
- t1 コミット (カウンター = 2)
ネストされたトランザクションでは、t1 が t1 COMMIT の後に ROLLBACK を発行すると、t2 のコミットも取り消されるため、counter は元の値に戻ります。