4

私がトランザクションを持っていると考えてください:

BEGIN TRANSACTION
DECLARE MONEY @amount
SELECT Amount AS @amount
  FROM Deposits
  WHERE UserId = 123
UPDATE Deposits
  SET Amount = @amount + 100.0
  WHERE UserId = 123
COMMIT

そして、2つのスレッドで次の順序で実行されます。

  1. スレッド1-選択
  2. スレッド2-選択
  3. スレッド1-更新
  4. スレッド2-更新

実行前の金額が0であると仮定します。

この場合、SQL Serverのさまざまな設定(コミットされていない読み取り、コミットされた読み取り、繰り返し可能な読み取り、シリアル化可能)で何が起こり、最後にいくらになるか、デッドロックが発生しますか?

4

5 に答える 5

2

他の人はすでに REPEATABLE READ の使用の問題に取り組んでいます。

そこで、別のアドバイスをします...

次のように 1 つのステートメントだけでなく、2 つのステートメントを使用するのはなぜですか?

UPDATE Deposits
SET Amount = Amount + 100.0
WHERE UserId = 123

また、あなたの実際のトランザクションは、UserID 以外の何かに基づいていますよね? そうしないと、最初に意図したよりも多くのレコードを処理するリスクがあります。

于 2008-11-03T17:59:15.083 に答える
2

うまく説明された素晴らしいシナリオ。私はそれをテストすることにしました。

これが私のセットアップスクリプトです:

CREATE TABLE Deposits(Amount Money, UserID int)
INSERT INTO Deposits (Amount, UserID)
SELECT 0.0, 123
--Reset
UPDATE Deposits
SET Amount = 0.00
WHERE UserID = 123

これが私のテストスクリプトです。

SET TRANSACTION ISOLATION LEVEL Serializable
----------------------------------------
-- Part 1
----------------------------------------
BEGIN TRANSACTION
DECLARE @amount MONEY
SET @amount =
(
SELECT Amount
FROM Deposits
WHERE UserId = 123
)
SELECT @amount as Amount
----------------------------------------
-- Part 2
----------------------------------------
DECLARE @amount MONEY
SET @amount =  *value from step 1*
UPDATE Deposits
SET Amount = @amount + 100.0
WHERE UserId = 123
COMMIT
SELECT *
FROM Deposits
WHERE UserID = 123

このテスト スクリプトを 2 つのクエリ アナライザー ウィンドウにロードし、質問の説明に従って各部分を実行しました。

すべての読み取りは書き込みの前に行われるため、すべてのスレッド/シナリオは 0 の値を @amount に読み取ります。

結果は次のとおりです。

コミットされた読み取り

1 T1.@Amount = 0.00
2 T1.@Amount = 0.00
3 Deposits.Amount = 100.00
4 Deposits.Amount = 100.00

コミットされていない読み取り

1 T1.@Amount = 0.00
2 T1.@Amount = 0.00
3 Deposits.Amount = 100.00
4 Deposits.Amount = 100.00

反復可能な読み取り

1 T1.@Amount = 0.00 (locks out changes by others on Deposit.UserID = 123)
2 T1.@Amount = 0.00 (locks out changes by others on Deposit.UserID = 123)
3 Hangs until step 4. (due to lock in step 2)
4 Deadlock!
Final result: Deposits.Amount = 100.00

シリアライズ可能

1 T1.@Amount = 0.00 (locks out changes by others on Deposit)
2 T1.@Amount = 0.00 (locks out changes by others on Deposit)
3 Hangs until step 4. (due to lock in step 2)
4 Deadlock!
Final result: Deposits.Amount = 100.00

思考シミュレーションを通じてこれらの結果に到達するために使用できる各タイプの説明を次に示します。

Read CommittedRead Uncommitedはどちらも、他のユーザーによる変更に対して読み取られたデータをロックしません。違いは、コミットされていない読み取りでは、まだコミットされていないデータを表示できること (欠点) と、読み取りに対して他のユーザーによってロックされているデータがある場合に読み取りをブロックしないことです (利点)。

Repeatable ReadSerializableはどちらも、読み取りのためにコミットされた読み取りのように動作します。ロックの場合、両方とも、他のユーザーによる変更に対して読み取られたデータをロックします。違いは、シリアル化可能なブロックは、読み取られた行よりも多く、以前には存在しなかったレコードを導入する挿入もブロックすることです。

したがって、反復可能な読み取りを使用すると、後の読み取りで新しいレコード (ファントム レコードと呼ばれる) を確認できます。serializable を使用すると、コミットするまでこれらのレコードの作成をブロックできます。

上記の説明は、このmsdn記事の私の解釈から来ています。

于 2008-11-03T18:25:01.880 に答える
1

はい、おそらく繰り返し読み取りが必要です。

私はおそらく楽観的ロックを介してこれを処理します。この場合、既存の値が読み取り時と同じ場合にのみ更新します(テストアンドセット)。値が同じでない場合は、エラーが発生します。これにより、デッドロックやデータ破損なしで、コミットされていない読み取りを実行できます。

BEGIN TRANSACTION
DECLARE MONEY @amount
SELECT Amount AS @amount
  FROM Deposits
  WHERE UserId = 123
UPDATE Deposits
  SET Amount = @amount + 100.0
  WHERE UserId = 123 AND Amount = @amount
IF @@ROWCOUNT <> 1 BEGIN ROLLBACK; RAISERROR(...) END
ELSE COMMIT END
于 2008-11-03T17:45:10.963 に答える
1

それ以外の場合は、デッドロックを回避するためにロック ヒントを使用できます (サーバーがコミット読み取りモードの場合)。

BEGIN TRANSACTION
DECLARE MONEY @amount
SELECT Amount AS @amount
  FROM Deposits WITH(UPDLOCK)
  WHERE UserId = 123
UPDATE Deposits
  SET Amount = @amount + 100.0
  WHERE UserId = 123
COMMIT

もちろん、この特定の手順では、(Kevin Fairchild が投稿したような) 単一のステートメントが優先され、副作用は発生しませんが、より複雑な状況では UPDLOCK ヒントが便利になる場合があります。

于 2008-11-03T18:31:00.323 に答える
0

レコードをロックする Repeatable read を使用したいと思います。最初の選択で値が取得され、完了するまでスレッド 2 のブロックが更新されます。したがって、あなたの例では 200 の最終結果

コミットされていない読み取りを行うと、両方のレコードが値 100 に設定されます。

コミットされた読み取りは、2 つのスレッドのタイミングに応じて、少し興味深い結果になる可能性があります....

これは、 Repeatable Readについても見つけた素晴らしい記事で、良い例を提供しています

于 2008-11-03T16:03:19.547 に答える