2011-12-19 13 views
2

次のsprocは、テーブルに行を挿入し、対応するテーブルのPKに使用されるランダムIDを生成しようとします。ランダムに生成されたIDとの衝突は、プロシージャが再試行され/再度呼び出されるcatchブロックで処理されます。ロックが長期間維持されるので、これには長い時間がかかり、デッドロックが発生します。再試行の直前にデッドロックを解放する方法があるので、他のスレッドがPKインデックスのロックに成功することができる短いウィンドウがありますか?再試行されたINSERTによるデッドロック


CREATE PROCEDURE addPerson 
    (
     @FirstName nvarchar(100), 
     @LastName nvarchar(100) 
    ) 
AS 
BEGIN 
    SET NOCOUNT ON; 

    DECLARE @PersonId int 

    -- generate random PersonId 
    -- this sproc can generate ids that already exist in the table 
    EXEC generateRandomPersonId @[email protected] OUTPUT 

    BEGIN TRY  
     INSERT INTO [dbo].[Persons] 
     (
      PersonId,FirstName,LastName 
     ) 
     VALUES 
     (
      @PersonId,@FirstName,@LastName 
     ) 

    BEGIN CATCH 
     -- 
     -- HOW TO RELEASE LOCKS HERE that are still held 
     -- for the previous INSERT statement? 
     -- 

     DECLARE @ErrorNumber int, @ErrorMessage nvarchar(2048) 
     SELECT @ErrorNumber=ERROR_NUMBER(), 
       @ErrorMessage=ERROR_MESSAGE() 

     -- if a race condition happened and 
     -- PersonId happened to be picked already, retry all over again 
     IF (@ErrorNumber = 2601 OR @ErrorNumber = 2627 AND CHARINDEX(N'PK_Persons_PersonId', @ErrorMessage) > 0) 
      BEGIN 
       -- 
       -- RETRYING HERE participates in a high possibility and 
       -- occurrence of deadlocks 
       -- 
       EXEC addPerson @FirstName,@LastName 
      END 
      ELSE 
       -- some other error, rethrow it 
       EXEC rethrowError 
      END 
    END CATCH 
END 
GO 
+0

は、商業用にこれですか生産環境製品? – danihp

+0

一意の制約違反に対していくつかのエラーコードが存在するのはどうしてですか?私はSQLサーバーをまったく知りません、ただ好奇心が強い – fge

+0

[トランザクション]を使用しない理由(http://beyondrelational.com/blogs/nakul/archive/2011/02/24/exception-handling-in-t-sql) try-catch-underappreciated-features-of-microsoft-sql-server.aspx)? 'try'の前にトランザクションを開始し、comment_を書く場所をロールバックします。 – danihp

答えて

2

プロセスは独自のロックをブロックしません。ストアドプロシージャの呼び出しは同じプロセスで実行されるため、2番目のinsertは最初のinsertからのロックを待機できません。

デッドロックグラフを投稿できますか?これは、ブロッキングプロセスに関する多くの情報を示しています。クイックフィックスとして

、あなたが最も(すべてではない)の可能な衝突を避けるだろう、ループ内の無料IDを検索できます。すべての

while 1=1 
    begin 
    EXEC generateRandomPersonId @[email protected] OUTPUT 
    if not exists (select * from Persons where PersonId = @PersonID) 
     break 
    end 
+0

+1 - これは、オペレータが何をすべきか、絶対に自由IDを検索してから、INSERTをします。 – Lamak

+0

それ自体ではなく、PKインデックスのロックを取得する必要のある別のプロシージャをブロックします。生成されたidが存在しないかどうかのチェックは、スレッドセーフではないため一意性が保証されません。 – kateroh

+0

IDが取得されていないことを確認するためのチェックを追加し、再帰呼び出しをループおよびボイラーに置き換えました!ありがとう – kateroh

1

まず、私は、この手順は意志が唯一の作品を願って学術的な環境ではなく、商用製品や実際の生産環境では使用できません。

これはアプローチです:

  • ループで再帰を置き換える
  • 使用取引

CREATE PROCEDURE addPerson 
    (
     @FirstName nvarchar(100), 
     @LastName nvarchar(100) 
    ) 
AS 

BEGIN 
    SET NOCOUNT ON; 

    DECLARE @doed bit 

    set @doed = 0 

    DECLARE @PersonId int 

    WHILE @doed = 0 

    BEGIN 
    -- generate random PersonId 
    -- this sproc can generate ids that already exist in the table 
    EXEC generateRandomPersonId @[email protected] OUTPUT 

    BEGIN TRANSACTION ExceptionHandling 
    BEGIN TRY  

      INSERT INTO [dbo].[Persons] 
      (
       PersonId,FirstName,LastName 
      ) 
      VALUES 
      (
       @PersonId,@FirstName,@LastName 
      ) 
      COMMIT TRANSACTION ExceptionHandling 
     BEGIN CATCH 

      ROLLBACK TRANSACTION ExceptionHandling 
      -- 
      -- HOW TO RELEASE LOCKS HERE that are still held 
      -- for the previous INSERT statement? 
      -- 

      DECLARE @ErrorNumber int, @ErrorMessage nvarchar(2048) 
      SELECT @ErrorNumber=ERROR_NUMBER(), 
        @ErrorMessage=ERROR_MESSAGE() 

         -- if a race condition happened and 
      -- PersonId happened to be picked already, retry all over again 
      IF !(@ErrorNumber = 2601 OR @ErrorNumber = 2627 AND CHARINDEX(N'PK_Persons_PersonId', @ErrorMessage) > 0) 
       BEGIN 
        -- 
        -- RETRYING HERE participates in a high possibility and 
        -- occurrence of deadlocks 
        set @doed = 0 
       END 
       ELSE 
        -- some other error, rethrow it 
        set @doed = 1 
        EXEC rethrowError 
       END 
     END CATCH 
    END --end while 
END 
GO​ 

免責事項:テストされていません

関連する問題