Comment simuler une impasse dans SQL Server en un seul processus?

notre code client détecte les blocages, attend un intervalle, puis réessaie la requête jusqu'à 5 fois. La logique de retry détecte les blocages basés sur le numéro d'erreur 1205.

mon but est de tester à la fois la logique de réessaiement des blocages et la manipulation des blocages à l'intérieur de diverses procédures stockées. Je peux créer une impasse en utilisant deux connexions différentes. Cependant, je voudrais simuler un blocage à l'intérieur d'une procédure stockée unique lui-même.

Un blocage soulève la message d'erreur suivant:

Msg 1205, Niveau 13, L'État 51, Ligne 1

La Transaction (numéro de processus 66) a été bloquée sur les ressources par un autre processus et a été choisie comme victime du blocage. Exécutez à nouveau l'opération.

je vois ce message d'erreur est 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.

Je ne peux pas soulever cette erreur en utilisant RAISERROR:

raiserror(1205, 13, 51)

Msg 2732, Niveau 16, État 1, Ligne 1

L'erreur numéro 1205 n'est pas valide. Le nombre doit être de 13000 à 2147483647 et il ne peut pas être 50000.

notre logique de Ré-essai de blocage vérifie si le numéro d'erreur est 1205. L'impasse doit avoir le même ID de message, le même niveau et le même état qu'une impasse normale.

Existe-t-il un moyen de simuler une impasse (avec RAISERROR ou tout autre moyen) et d'obtenir le même numéro de message avec un seul processus?

nos bases de données utilisent la compatibilité SQL 2005, bien que nos serveurs varient de 2005 à 2008 R2.

25
demandé sur Paul Williams 2012-07-20 01:59:40

5 réponses

exécutez le script ci-dessous dans une fenêtre SQL Server Management Studio. (Testé sur 2008 R2 seulement.), Vous pouvez le laisser fonctionner aussi longtemps que nécessaire.

à l'endroit où vous voulez simuler une impasse, insérez un appel à sp_simulatedeadlock. Exécutez votre processus, et l'impasse devrait produire.

lorsque le test est terminé, arrêtez la requête SSMS et lancez le code de nettoyage en bas.

/*
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
47
répondu Paul Williams 2012-08-07 16:09:26

Vous pouvez exploiter un bug que Microsoft ne semble pas pressé de corriger en exécutant

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

CE SQL va causer une impasse avec lui-même. Beaucoup plus de détails sur le blog D'Aaron Bertand http://sqlperformance.com/2013/11/t-sql-queries/single-tx-deadlock

10
répondu Michael J Swart 2016-09-02 19:51:35

(Apparemment je n'ai pas assez de réputation pour ajouter un commentaire. Donc poster comme réponse.)

une impasse nécessite au moins deux processus. la seule exception étant les blocages parallèles intra-query qui sont en quelque sorte impossibles à reproduire.

cependant vous pouvez simuler une impasse sur deux processus exécutant la même requête (ou sp). Quelques idées ici

5
répondu Roji P Thomas 2015-09-21 16:23:33

Paul, merci pour votre question et de suivi de la réponse. Votre message m'a inspiré à rejoindre Stack Overflow pour la première fois.

j'ai eu quelques difficultés à obtenir votre réponse au travail, et je veux juste partager les petits changements que j'ai faits pour obtenir le travail. Si ça sauve quelqu'un un jour de sa vie, alors ça en vaut la peine. La clé est de lancer et de faire reculer la transaction sp_simulatedeadlock dans le cadre de la procédure elle-même. Je n'ai fait aucun changement à votre procédure mentionnée dans votre réponse.

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
1
répondu Vijay Govindan 2016-08-26 16:26:22

façon la plus simple de se reproduire en C# avec parallèle par exemple,

    var List = ... (add some items with same ids)

    Parallel.ForEach(List, 
        (item) =>
    {

        ReportsDataContext erdc = null;
        try
        {
            using (TransactionScope scope = new TransactionScope())
            {
                erdc = new ReportsDataContext("....connection....");
                var report = erdc.Report.Where(x => x.id == item.id).Select(x => x);
                report.Count++
                erdc.SubmitChanges();

                scope.Complete();
            }

            if (erdc != null)
                erdc.Dispose();
        }
        catch (Exception ex)
        {
            if (erdc != null)
                erdc.Dispose();
            ErrorLog.LogEx("multi thread victim", ex);
        }

plus d'intérêt comment prévenir cette erreur en situation de vrai fil croisé?

0
répondu user1005462 2018-01-29 14:50:02