39

Read Committed スナップショット トランザクション分離設定を想定すると、次のステートメントは同時インクリメントを「失う」ことがないという意味で「アトミック」ですか?

update mytable set counter = counter + 1

この更新ステートメントがより大きなトランザクションの一部である一般的なケースでは、そうではないと思います。たとえば、次のシナリオが可能だと思います。

  • トランザクション #1 内のカウンターを更新します
  • トランザクション#1で他のことをする
  • トランザクション #2 でカウンターを更新する
  • コミット トランザクション #2
  • トランザクション #1 をコミットする

この場合、カウンターが 1 だけ増えるのではないでしょうか。それがトランザクション内の唯一のステートメントである場合、違いはありますか?

stackoverflow のようなサイトは、質問ビュー カウンターでこれをどのように処理しますか? それとも、いくつかのインクリメントを「失う」可能性は許容できると考えられていますか?

4

5 に答える 5

33

MSSQL ヘルプによると、次のように実行できます。

UPDATE tablename SET counterfield = counterfield + 1 OUTPUT INSERTED.counterfield

これにより、フィールドが 1 つずつ更新され、更新された値が SQL レコードセットとして返されます。

于 2010-04-23T02:27:53.687 に答える
15

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 は元の値に戻ります。

于 2008-10-11T00:21:51.717 に答える
3

いいえ、ちがいます。値は共有モードで読み取られ、次に排他モードで更新されるため、複数の読み取りが発生する可能性があります。

Serializable レベルを使用するか、次のようなものを使用します

update t
set counter = counter+1
from t with(updlock, <some other hints maybe>)
where foo = bar
于 2008-10-10T22:45:00.583 に答える
1

トランザクションの中心には、最も外側のトランザクションが 1 つだけあります。内部トランザクションは、トランザクション内のチェックポイントに似ています。分離レベルは、親/子関連のトランザクションではなく、最も外側の兄弟トランザクションにのみ影響します。

カウンターは 2 つ増加します。次の例では、値が (Num = 3) の 1 つの行が生成されます。(私は SMSS を開き、それをローカルの SQL Server 2008 Express インスタンスに向けました。テスト用に Playground という名前のデータベースを持っています。)

use Playground

drop table C
create table C (
    Num int not null)

insert into C (Num) values (1)

begin tran X
    update C set Num = Num + 1
    begin tran Y
        update C set Num = Num + 1
    commit tran Y
commit tran X

select * from C
于 2008-10-11T00:43:50.733 に答える