2

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


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 @PersonId=@PersonId 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
4

2 に答える 2

2

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

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

簡単な修正として、ループ内で空きIDを検索できます。これにより、発生する可能性のあるほとんどの(すべてではない)衝突を回避できます。

while 1=1
    begin
    EXEC generateRandomPersonId @PersonId=@PersonId OUTPUT
    if not exists (select * from Persons where PersonId = @PersonID)
        break
    end
于 2011-12-19T21:02:45.887 に答える
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 @PersonId=@PersonId 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​

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

于 2011-12-19T21:12:24.260 に答える