How to simulate a deadlock in SQL Server in a single process?

Viewed 28636

Our client side code detects deadlocks, waits for an interval, then retries the request up to 5 times. The retry logic detects the deadlocks based on the error number 1205.

My goal is to test both the deadlock retry logic and deadlock handling inside of various stored procedures. I can create a deadlock using two different connections. However, I would like to simulate a deadlock inside of a single stored procedure itself.

A deadlock raises the following error message:

Msg 1205, Level 13, State 51, Line 1
Transaction (Process ID 66) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

I see this error message is in 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.

I can't raise this error using RAISERROR:

raiserror(1205, 13, 51)

Msg 2732, Level 16, State 1, Line 1
Error number 1205 is invalid. The number must be from 13000 through 2147483647 and it cannot be 50000.

Our deadlock retry logic checks if the error number is 1205. The deadlock needs to have the same message ID, level, and state as a normal deadlock.

Is there a way to simulate a deadlock (with RAISERROR or any other means) and get the same message number out with just one process?

Our databases are using SQL 2005 compatibility, though our servers vary from 2005 through 2008 R2.

7 Answers

This works reliably from a single session. Use service broker activation to invoke the second thread which is required for a deadlock.

NOTE1: cleanup script not included
NOTE2: service broker has to be enabled: ALTER DATABASE dbname SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;

EXEC sp_executesql N'
CREATE OR ALTER PROCEDURE DeadlockReceive 
AS
DECLARE @MessageBody NVARCHAR(1000);
RECEIVE @MessageBody = CAST(message_body AS NVARCHAR(1000) )FROM DeadlockQueue
SELECT @MessageBody
EXEC sp_executesql @MessageBody;'

IF EXISTS (SELECT * FROM sys.services WHERE name = 'DeadlockService') DROP SERVICE DeadlockService
IF OBJECT_ID('DeadlockQueue') IS NOT NULL DROP QUEUE dbo.DeadlockQueue
IF EXISTS (SELECT * FROM sys.service_contracts WHERE name = 'DeadlockContract') DROP CONTRACT DeadlockContract
IF EXISTS (SELECT * FROM sys.service_message_types WHERE name = 'DeadlockMessage') DROP MESSAGE TYPE DeadlockMessage
DROP TABLE IF EXISTS DeadlockTable1 ;
DROP TABLE IF EXISTS DeadlockTable2 ;

CREATE MESSAGE TYPE DeadlockMessage VALIDATION = NONE;
CREATE QUEUE DeadlockQueue WITH STATUS = ON, ACTIVATION (PROCEDURE_NAME = DeadlockReceive, EXECUTE AS SELF, MAX_QUEUE_READERS = 1);
CREATE CONTRACT DeadlockContract AUTHORIZATION dbo (DeadlockMessage SENT BY ANY);
CREATE SERVICE DeadlockService ON QUEUE DeadlockQueue (DeadlockContract);

CREATE TABLE DeadlockTable1 (Value INT); INSERT dbo.DeadlockTable1 SELECT 1;
CREATE TABLE DeadlockTable2 (Value INT); INSERT dbo.DeadlockTable2 SELECT 1;

DECLARE @ch UNIQUEIDENTIFIER
BEGIN DIALOG @ch FROM SERVICE DeadlockService TO SERVICE 'DeadlockService' ON CONTRACT DeadlockContract WITH ENCRYPTION = OFF ;
SEND ON CONVERSATION @ch MESSAGE TYPE DeadlockMessage (N'
set deadlock_priority high;
begin tran; 
update DeadlockTable2 set value = 5;
waitfor delay ''00:00:01'';
update DeadlockTable1 set value = 5;
commit')

SET DEADLOCK_PRIORITY LOW
BEGIN TRAN
    UPDATE dbo.DeadlockTable1 SET Value = 2
    waitfor delay '00:00:01';
    UPDATE dbo.DeadlockTable2 SET Value = 2
COMMIT

If you happen to run into problems with the GO/go keywords (Incorrect syntax near 'GO') in any of the scripts above, it's important to know, that this instruction only seems to work in Microsoft SQL Server Management Studio/sqlcmd:

The GO keyword is not T-SQL, but a SQL Server Management Studio artifact that allows you to separate the execution of a script file in multiple batches.I.e. when you run a T-SQL script file in SSMS, the statements are run in batches separated by the GO keyword. […]

SQL Server doesn't understand the GO keyword. So if you need an equivalent, you need to separate and run the batches individually on your own.

(from JotaBe's answer to another question)

So if you want to try out e.g. Michael J Swart's Answer through DBeaver for example, you'll have to remove the go's and run all parts of the query on its own.

Related