Notre code côté client détecte les blocages, attend un intervalle, puis relance la demande jusqu'à 5 fois. La logique de nouvelle tentative détecte les blocages en fonction du numéro d'erreur 1205.
Mon objectif est de tester à la fois la logique de nouvelle tentative d'interblocage et la gestion des interblocages dans diverses procédures stockées. Je peux créer un blocage en utilisant deux connexions différentes. Cependant, je voudrais simuler un blocage dans une procédure stockée unique elle-même.
Un blocage entraîne le message d'erreur suivant:
Msg 1205, niveau 13, état 51, ligne 1
La transaction (ID de processus 66) était blocage des ressources de verrouillage avec un autre processus et a été choisi en tant que victime de l'impasse. Relancez la transaction.
Je vois que ce message d'erreur est dans 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 générer cette erreur avec RAISERROR
:
raiserror(1205, 13, 51)
Msg 2732, Niveau 16, Etat 1, Ligne 1
Le numéro d'erreur 1205 est invalide . Le numéro doit être compris entre 13000 et 2147483647 et ne peut pas être 50000.
Notre logique de nouvelle tentative d'interblocage vérifie si le numéro d'erreur est 1205. Il doit avoir les mêmes ID, niveau et état de message qu'un blocage normal.
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.
Comme beaucoup l'ont souligné, la réponse est non, un processus unique ne peut pas de manière fiable lui-même aboutir à une impasse. J'ai proposé la solution suivante pour simuler une impasse sur un système de développement ou de test.
Exécutez le script ci-dessous dans une fenêtre SQL Server Management Studio. (Testé sur 2008 R2 uniquement.) Vous pouvez le laisser fonctionner aussi longtemps que nécessaire.
À l'endroit où vous souhaitez simuler un blocage, insérez un appel à sp_simulatedeadlock
. Exécutez votre processus et l'impasse devrait se produire.
Une fois les tests terminés, arrêtez la requête SSMS et exécutez le code de nettoyage situé 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
Vous pouvez exploiter un bogue 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 provoquera un blocage avec lui-même. Plus de détails sur le blog d'Aaron Bertand http://sqlperformance.com/2013/11/t-sql-queries/single-tx-deadlock
(Apparemment, je n'ai pas assez de réputation pour ajouter un commentaire. Publiant comme réponse.)
Une impasse nécessite au moins deux processus. la seule exception étant les impasses parallèles intra-requête qui sont un peu impossibles à reproduire.
Cependant, vous pouvez simuler un blocage sur deux processus exécutant la même requête (ou sp). Quelques idées ici
Paul, merci pour votre question et votre réponse. Votre message m'a inspiré de rejoindre Stack Overflow pour la première fois.
J'ai eu un peu de difficulté à obtenir votre réponse au travail et je veux simplement partager les petits changements que j'ai apportés pour que cela fonctionne. Si cela sauve un jour de sa vie, cela en vaut la peine. La clé est de commencer et d’annuler la transaction sp_simulatedeadlock dans la procédure elle-même. Je n'ai apporté aucune modification à 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
Le moyen le plus simple de reproduire en C # avec Parallel 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 empêcher cette erreur dans la situation réelle de cross-thread?