29

クライアント側のコードはデッドロックを検出し、一定の間隔で待機してから、リクエストを最大 5 回再試行します。再試行ロジックは、エラー番号 1205 に基づいてデッドロックを検出します。

私の目標は、さまざまなストアド プロシージャ内のデッドロック再試行ロジックとデッドロック処理の両方をテストすることです。2 つの異なる接続を使用してデッドロックを作成できます。ただし、単一のストアド プロシージャ自体の内部でデッドロックをシミュレートしたいと考えています。

デッドロックにより、次のエラー メッセージが表示されます。

メッセージ 1205、レベル 13、状態 51、行 1
トランザクション (プロセス ID 66) は、別のプロセスとのロック リソースでデッドロックされ、デッドロックの犠牲者として選択されました。トランザクションを再実行します。

次のエラー メッセージが表示されますsys.messages

select * from sys.messages where message_id = 1205 and language_id = 1033

message_id language_id severity  is_event_logged   text
1205       1033        13        0                 Transaction (Process ID %d) was deadlocked on %.*ls resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

を使用してこのエラーを発生させることはできませんRAISERROR:

raiserror(1205, 13, 51)

メッセージ 2732、レベル 16、状態 1、行 1
エラー番号 1205 は無効です。番号は 13000 から 2147483647 の範囲である必要があり、50000 にすることはできません。

デッドロックの再試行ロジックは、エラー番号が 1205 かどうかをチェックします。デッドロックには、通常のデッドロックと同じメッセージ ID、レベル、および状態が必要です。

(RAISERROR またはその他の手段を使用して) デッドロックをシミュレートし、1 つのプロセスだけで同じメッセージ番号を取得する方法はありますか?

データベースは SQL 2005 互換性を使用していますが、サーバーは 2005 から 2008 R2 までさまざまです。

4

6 に答える 6

54

多くの人が指摘しているように、答えはノーです。単一のプロセスがそれ自体を確実にデッドロックすることはできません。開発システムまたはテストシステムのデッドロックをシミュレートするために、次のソリューションを思いつきました。

SQL ServerManagementStudioウィンドウで以下のスクリプトを実行します。(2008 R2でのみテストされています。)必要な限り、実行したままにしておくことができます。

デッドロックをシミュレートする場所に、への呼び出しを挿入しますsp_simulatedeadlock。プロセスを実行すると、デッドロックが発生するはずです。

テストが完了したら、SSMSクエリを停止し、下部にあるクリーンアップコードを実行します。

/*
This script helps simulate deadlocks.  Run the entire script in a SQL query window.  It will continue running until stopped.
In the target script, insert a call to sp_simulatedeadlock where you want the deadlock to occur.
This stored procedure, also created below, causes the deadlock.
When you are done, stop the execution of this window and run the code in the cleanup section at the bottom.
*/
set nocount on

if object_id('DeadlockTest') is not null
    drop table DeadlockTest

create table DeadlockTest
(
    Deadlock_Key int primary key clustered,
    Deadlock_Count int
)
go

if exists (select * from sysobjects where id = object_id(N'sp_simulatedeadlock')
           AND objectproperty(id, N'IsProcedure') = 1)
drop procedure sp_simulatedeadlock
GO

create procedure sp_simulatedeadlock
(
    @MaxDeadlocks int = -1 -- specify the number of deadlocks you want; -1 = constant deadlocking
)
as begin

    set nocount on

    if object_id('DeadlockTest') is null
        return

    -- Volunteer to be a deadlock victim.
    set deadlock_priority low

    declare @DeadlockCount int

    select @DeadlockCount = Deadlock_Count -- this starts at 0
    from DeadlockTest
    where Deadlock_Key = 2

    -- Trace the start of each deadlock event.
    -- To listen to the trace event, setup a SQL Server Profiler trace with event class "UserConfigurable:0".
    -- Note that the user running this proc must have ALTER TRACE permission.
    -- Also note that there are only 128 characters allowed in the trace text.
    declare @trace nvarchar(128)

    if @MaxDeadlocks > 0 AND @DeadlockCount > @MaxDeadlocks
    begin

        set @trace = N'Deadlock Test @MaxDeadlocks: ' + cast(@MaxDeadlocks as nvarchar) + N' @DeadlockCount: ' + cast(@DeadlockCount as nvarchar) + N' Resetting deadlock count.  Will not cause deadlock.'
        exec sp_trace_generateevent
            @eventid = 82,  -- 82 = UserConfigurable:0 through 91 = UserConfigurable:9
            @userinfo = @trace

        -- Reset the number of deadlocks.
        -- Hopefully if there is an outer transaction, it will complete and persist this change.
        update DeadlockTest
        set Deadlock_Count = 0
        where Deadlock_Key = 2
        return
    end

    set @trace = N'Deadlock Test @MaxDeadlocks: ' + cast(@MaxDeadlocks as nvarchar) + N' @DeadlockCount: ' + cast(@DeadlockCount as nvarchar) + N' Simulating deadlock.'
    exec sp_trace_generateevent
        @eventid = 82,  -- 82 = UserConfigurable:0 through 91 = UserConfigurable:9
        @userinfo = @trace

    declare @StartedTransaction bit
    set @StartedTransaction = 0
    if @@trancount = 0
    begin
        set @StartedTransaction = 1
        begin transaction
    end

    -- lock 2nd record
    update DeadlockTest
    set Deadlock_Count = Deadlock_Count
    from DeadlockTest
    where Deadlock_Key = 2

    -- lock 1st record to cause deadlock
    update DeadlockTest
    set Deadlock_Count = Deadlock_Count
    from DeadlockTest
    where Deadlock_Key = 1

    if @StartedTransaction = 1
        rollback    
end
go

insert into DeadlockTest(Deadlock_Key, Deadlock_Count)
select 1, 0
union select 2, 0

-- Force other processes to be the deadlock victim.
set deadlock_priority high

begin transaction

while 1 = 1
begin

    begin try

        begin transaction

        -- lock 1st record
        update DeadlockTest
        set Deadlock_Count = Deadlock_Count
        from DeadlockTest
        where Deadlock_Key = 1

        waitfor delay '00:00:10'

        -- lock 2nd record (which will be locked when the target proc calls sp_simulatedeadlock)
        update DeadlockTest
        set Deadlock_Count = Deadlock_Count
        from DeadlockTest
        where Deadlock_Key = 2

        rollback

    end try
    begin catch
        print 'Error ' + convert(varchar(20), ERROR_NUMBER()) + ': ' + ERROR_MESSAGE()
        goto cleanup
    end catch

end

cleanup:

if @@trancount > 0
    rollback

drop procedure sp_simulatedeadlock
drop table DeadlockTest
于 2012-08-07T16:09:26.587 に答える
19

実行することで、Microsoft が急いで修正しないと思われるバグを悪用できます。

use tempdb

begin tran
go

CREATE TYPE dbo.IntIntSet AS TABLE(
    Value0 Int NOT NULL,
    Value1 Int NOT NULL
)
go

declare @myPK dbo.IntIntSet;
go

rollback

この SQL は、それ自体でデッドロックを引き起こします。詳細については、Aaron Bertand のブログhttp://sqlperformance.com/2013/11/t-sql-queries/single-tx-deadlockを参照してください。

于 2016-09-02T19:51:35.733 に答える
6

(どうやら私はコメントを追加するのに十分な評判がありません。それで答えとして投稿します。)

デッドロックには、少なくとも2つのプロセスが必要です。唯一の例外は、再現が不可能なクエリ内並列デッドロックです。

ただし、まったく同じクエリ(またはsp)を実行している2つのプロセスでデッドロックをシミュレートできます。ここにいくつかのアイデア

于 2012-07-21T01:00:16.300 に答える
1

ポールの答えを得るのに苦労しました。私はそれを機能させるためにいくつかの小さな変更を加えました。

重要なのは、プロシージャ自体の中で sp_simulatededlock トランザクションを開始してロールバックすることです。ポールの回答の手順を変更しませんでした。

DECLARE @DeadlockCounter INT = NULL

SELECT @DeadlockCounter = 0

WHILE @DeadlockCounter < 10
BEGIN
    BEGIN TRY
    /* The procedure was leaving uncommitted transactions, I rollback the transaction in the catch block */
        BEGIN tran simulate
            Exec sp_simulatedeadlock

        /* Code you want to deadlock */

        SELECT @DeadlockCounter = 10
    END TRY
    BEGIN CATCH
        Rollback tran simulate

        PRINT ERROR_MESSAGE()

        IF (ERROR_MESSAGE() LIKE '%deadlock%' OR ERROR_NUMBER() = 1205) AND @DeadlockCounter < 10
            BEGIN
                SELECT @DeadlockCounter +=1
                PRINT @DeadlockCounter

                IF @DeadlockCounter = 10
                BEGIN
                    RAISERROR('Deadlock limit exceeded or error raised', 16, 10);
                END
            END
    END CATCH
END
于 2016-08-26T16:26:22.367 に答える