2017-02-20 9 views
1

私はMSSQLの値をインクリメントしようとしています(例、ギフトカードを購入した後にユーザーの残高を増やす)。私は単純化のためテーブル内の1つの行を持っていると私はすべての行をインクリメント単一テーブルストアドプロシージャデッドロック

CREATE TABLE Test_Table ([intCount] [int] NOT NULL) 

ALTER PROCEDURE Test_Proc AS 
BEGIN 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 
    BEGIN TRAN 
     UPDATE Test_Table 
     SET intCount = intCount + 1 
    COMMIT TRAN 
    SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 

    RETURN(0) 
END 

私のストアドプロシージャとテーブルは次のようになり。

私はC#で10スレッドを生成し、各スレッドでストアドプロシージャを10回呼び出します。しかし、ほとんどのスレッドでデッドロックが発生します。

for (int thread = 0; thread < threads; thread++) 
{ 
    new Thread(() => 
    { 
     try 
     { 
      for (int ix = 0; ix < count; ix++) 
      { 
       using (var conn = new SqlConnection(connectionString)) 
       { 
        conn.Open(); 
        SqlCommand cmd = new SqlCommand("Test_Proc", conn); 
        cmd.CommandType = CommandType.StoredProcedure; 

        cmd.ExecuteNonQuery(); 
       } 
      } 
     } 
     catch (Exception e) 
     { 
      errors++; 
     } 
    }).Start(); 
} 

私が(UPDLOCK、HOLDLOCK)で試みたが、それは、デッドロックの頻度を減らすようには見えなかった。このストアドプロシージャを呼び出す私のコードは次のようになります。

私はこれらのデッドロックを防ぐためにストアドプロシージャを変更できますか?私は本当にC#ですべてのストアドプロシージャ呼び出しをシリアライズするのではなく、SQLの答えを探しています。

(これはDeadlock with single stored procedure and multiple threadsと似ていますが、デッドロックを回避したいと思っている間は、デッドロックの方法を明示的に求めています)。

EDIT:ストアドプロシージャの内容をトランザクションに入れるようにコードを変更しましたが、それでもデッドロックが発生しました。

EDIT:エラーメッセージはTransaction (Process ID 124) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.のように見えます。

EDIT:フィードバックごとにストアドプロシージャを更新しましたが、それでもデッドロックが発生します。

EDIT:説明は次のとおりです:https://stackoverflow.com/a/36831413/1117119。 SERIALIZABLEは共有ロックを取得し、書き込みを行うときに排他ロックに変換します。これにより、デッドロックが発生します。

また、デッドロックが発生するのは、単にスレッドが多すぎるためです(SQL Server 2008: Getting deadlocks... without any locks)。

+1

これをチェックすると、同じルートの問題であるように見えます(http://stackoverflow.com/a/41594231/1158842)。SQLレベルでそれを実行したい場合は、sp_getapplockに向かう可能性があります。 –

+0

最初に現在の値を '選択 'する必要はありません。 UPDATE Test_Table SET intCount = intCount + 1'で十分です。あなたは「*デッドロック*」と言っていますが、実際には、SQL Serverがデッドロックを検出したために1つのトランザクションが強制終了されたという例外がありますか?はいの場合は、あなたの質問にその例外のエラーメッセージを追加してください。同時に複数のトランザクションから同じ行を更新することはできないため、スレッドは待機し、2番目の更新は常に前の更新が完了するまで待つ必要があるため待機します。 –

答えて

1

私はあなたが私たちを示してきたものの問題のちょうどsimlifiedバージョンであることを理解し、とにかくかどうか代わりに別のSELECTで考えると、あなたはすなわち、単一UPDATEとしてそれを行うことができるしているUPDATE:

UPDATE Test_Table SET intCount += 1 

これが不可能な場合は、両方の文を1つのトランザクションに囲みます。

EDIT: 変換ロックのデッドロックを回避するには、with(xlock, tablock)ヒントをSELECTステートメントに追加します。

+0

お返事ありがとうございます。ストアドプロシージャの本体をトランザクションに入れるようにコードを変更しましたが、以前と同じくらい頻繁にデッドロックが発生します。 – yeerk

0

ディーンの答えが最も正確であるが、場合には他の誰が正確に別々のSELECTとUPDATEの仕事を作る方法を知りたい、これは私がなってしまったコードです:

BEGIN TRAN 

    DECLARE @x int 

    select @x = intCount 
    from Test WITH (TABLOCKX, UPDLOCK) 
    WHERE id = 1  

    UPDATE Test 
    SET intCount = @x + 1 
    WHERE id = 1  

COMMIT TRAN 

これが解決さ排他的ロックを取らない最初の選択の問題(https://stackoverflow.com/a/36831413/1117119)。

スレッド数が多すぎるとロックを取得するコードがデッドロックするようです(SQL Server 2008: Getting deadlocks... without any locks)。