Как имитировать взаимоблокировку в SQL Server в одном процессе?
наш клиентский код обнаруживает тупики, ожидает интервал, затем повторяет запрос до 5 раз. Логика повтора обнаруживает тупики на основе номера ошибки 1205.
моя цель-проверить логику повтора взаимоблокировки и обработку взаимоблокировки внутри различных хранимых процедур. Я могу создать взаимоблокировку, используя два разных соединения. Тем не менее, я хотел бы смоделировать тупик внутри одной хранимой процедуры.
тупик ставит следующее сообщение об ошибке:
Msg 1205, Уровень 13, Состояние 51, Строка 1
Транзакция (идентификатор процесса 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)
Msg 2732, Уровень 16, Состояние 1, Строка 1
Номер ошибки 1205 недопустим. Число должно быть от 13000 до 2147483647 и не может быть 50000.
наш тупик логику проверяет, является ли номер ошибки 1205. Взаимоблокировка должна иметь тот же идентификатор сообщения, уровень и состояние, что и обычная взаимоблокировка.
есть ли способ имитировать тупик (с RAISERROR или любыми другими средствами) и получить тот же номер сообщения только с одним процессом?
наши базы данных используют совместимость SQL 2005, хотя наши серверы варьируются от 2005 до 2008 R2.
5 ответов
как указывали многие, ответ-нет, один процесс не может надежно заблокировать себя. Я придумал следующее решение для имитации тупика в разработке или тестовой системе..
запустите приведенный ниже сценарий в окне SQL Server Management Studio. (Протестировано только на 2008 R2.) Вы можете оставить его работать столько, сколько необходимо.
в том месте, где вы хотите имитировать взаимоблокировку, вставьте вызов sp_simulatedeadlock
. Запустите свой процесс, и deadlock должен происходить.
после завершения тестирования остановите запрос 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
вы можете использовать ошибку, которую 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 вызовет взаимоблокировку с самим собой. Много больше деталей в блоге Аарона Бертандаhttp://sqlperformance.com/2013/11/t-sql-queries/single-tx-deadlock
(видимо, у меня недостаточно репутации, чтобы добавить комментарий. Поэтому публикация в качестве ответа.)
взаимоблокировка требует по крайней мере двух процессов. единственным исключением являются параллельные блокировки внутри запроса, которые невозможно воспроизвести.
однако вы можете имитировать взаимоблокировку двух процессов, выполняющих один и тот же запрос (или sp). Некоторые идеи здесь
Павел, спасибо за ваш вопрос и последующий ответ. Ваш пост вдохновил меня впервые присоединиться к Stack Overflow.
У меня были некоторые трудности с получением вашего ответа на работу, и я просто хочу поделиться небольшими изменениями, которые я сделал, чтобы заставить его работать. Если это спасает кому-то день их жизни, то это того стоит. Ключ должен начать и откатить транзакцию sp_simulatedeadlock в самой процедуре. Я не внес никаких изменений в вашу процедуру, упомянутую в ответ.
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
самый простой способ воспроизведения в C# с параллельным например,
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);
}
больше интереса как предотвратить эту ошибку в реальной ситуации перекрестного потока?