Deadlock Internals with Real Time Scenario – Explanation and Resolution

What is deadlock?
A deadlock is a situation in which two or more competing actions are each waiting for the other to finish, and thus neither ever does.
Refer: http://en.wikipedia.org/wiki/Deadlock

Scenario:
Data Preparation Script: (Script-a)

[sql]
–Create Sample Database
CREATE DATABASE SampleDeadLockDatabase
GO

USE SampleDeadLockDatabase
GO

–Create Sample Tables
CREATE TABLE SampleTable1(ID INT IDENTITY(1,1),VALUE UNIQUEIDENTIFIER)
GO

CREATE TABLE SampleTable2(ID INT IDENTITY(1,1),VALUE UNIQUEIDENTIFIER)
GO

–Populate Sample tables
INSERT INTO SampleTable1(VALUE)
SELECT NEWID()
GO 10

INSERT INTO SampleTable2(VALUE)
SELECT NEWID()
GO 10

–Retreive data for verification
SELECT * FROM SampleTable1
SELECT * FROM SampleTable2
[/sql]

Transaction1 Script: (Script-b)

[sql]
Select @@SPID — Note down this value here (68)
——————Transaction1——————-
–Step1 Begins–
USE SampleDeadLockDatabase
GO
BEGIN TRAN

UPDATE SampleTable1
SET VALUE = ‘1DF1EBE1-322E-4698-A5BD-2E2C6D85AEB6’
WHERE ID=1
–Step1 Ends–

—RUN Step2 in Transaction2

–Step3 Begins–
SELECT * FROM SampleTable2 WHERE ID=4

ROLLBACK TRAN
–Step3 Ends–
——————Transaction1——————-

[/sql]

Transaction2 Script: (Script-c)

[sql]
Select @@SPID — Note down this value here — 65
——————Transaction2——————-
–Step2 Begins–
USE SampleDeadLockDatabase
GO
BEGIN TRAN

UPDATE SampleTable2
SET VALUE = ‘23154CCE-0919-4D3A-92EB-E6B4062F8715’
WHERE ID=3
–Step2 Ends–

–Step4 Begins–
SELECT * FROM SampleTable1 WHERE ID=5

ROLLBACK TRAN
–Step4 Ends–
——————Transaction2——————-
[/sql]

Data Preparation:
In SQL Server Management Studio, Click on new query
Paste the Data Preparation Script, and execute it.

Dead Lock Scenario Creation:
Paste the two code snippets transaction1 and transaction 2 in separate query windows.
In each of them highlight the code (SELECT @@SPID) and run it to note down the SPID
Now run the Step1 in transaction1
This step updates SampleTable1 with a new value for id 1
Now run the Step2 in transaction2
This step updates SampleTable2 with a new value for id 3
Now run the Step3 in transaction1
This step selects the values of SampleTable2 with id 4
You will notice that this step3 is still executing. Why? We will analyze this later in the post.
Now run the Step4 in transaction2
This step selects the values of SampleTable1 with id 5
You will notice that this step executes for few seconds and completes. Also one of the session (Transaction1 or Transaction2 will be deadlocked.) with the errormessage


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

Analysis:
Why Step3 was waiting (Blocked?)
So now let us replicate the scenario again, by running the above steps1 and 2.
In the new query window (again a new Query window 🙂 ) Run the below script to understand what locks are placed on the tables pages and rows.

Script-d
[sql]
–Update this id with the @@spid of transaction1
SELECT resource_type,
resource_description,
resource_associated_entity_id,
request_mode,
request_type,
request_status FROM sys.dm_tran_locks
where request_session_id = 68

–Update this id with the @@spid of transaction2
SELECT resource_type,
resource_description,
resource_associated_entity_id,
request_mode,
request_type,
request_status FROM sys.dm_tran_locks
where request_session_id = 65

–Fetch the partition_id of the table
select p.partition_id,t.name,t.object_id From sys.partitions p join sys.tables t
on t.object_id = p.object_id
where t.name in (‘SampleTable1′,’SampleTable2’)
[/sql]

Update the values above with the respective @@spud’s that we noted down earlier. (SPID’s of the respective queries)
Output:

Table Locks

When you execute this you can find that each session has created exclusive lock (X) on the row level and intent exclusive (IX) locks on the page and table level.
Let’s find what exactly those rows are.
Copy the resource_description values for resource_type RID to the below script and run it.

Script-e:
[sql]
select * From SampleTable1 (nolock) where %%lockres%% = ‘1:154:0’
select * From SampleTable2 (nolock) where %%lockres%% = ‘1:156:2’
[/sql]

This would output the rows that we updated in respective transactions.

Lockres Output

Now run the step3 in transaction1, and run the script-d pasted above.
Output:
Shared Lock Waiting

You will notice that transaction1 now has the new lock for RID 1:156:2 which is in waiting state.
It’s waiting for an exclusive lock to be released on a row on SampleTable2
In order to find out the row for which transaction 1 is waiting copy the resource_description values to the below script and run it.
Script-f:
[sql]
select * From SampleTable2 (nolock) where %%lockres%% = ‘1:156:2’
[/sql]

Shared Lock Waiting For Row

So transaction1 is waiting for exclusive lock on row ID 3 to be released.
But in no way we are referring to rowid3 of transaction1., we were actually referring to row ID 4.

Why is SQL Server waiting for this row?

In order to understand this stop the step3 in transaction1 (it’s still getting executed) , select step3 code and click on Display estimated execution plan button ( or Query – Display estimated execution plan button) (or Ctrl + L )
You will find a table scans in the execution plan.

Waiting Transaction Execution Plan

So this table scan is forcing SQL Server to actually scan all the rows in the table(for ID=4) , as the ID=3 is blocked by transaction 2 it is waiting for transaction 2 to actually complete in order to take shared lock on the row and compare its value with 4.

Similarly when step4 is executed it is blocked by transaction 1, as step 4 has to do a table scan on the table SampleTable1 for ID = 5, but the transaction 1 has exclusively locked the row ID = 1.
So,
Transaction 1 is blocked by Transaction 2
AND
Transaction 2 is blocked by Transaction 1
This causes deadlock situation.
As both transactions would stay for infinite time blocking each other.
So, one of the transaction that is easier to rollback is terminated by SQL Server giving deadlock error: 1205

Resolutions:
1. Change to Snapshot Isolation.
[sql]
ALTER DATABASE SampleDeadLockDatabase
SET READ_COMMITTED_SNAPSHOT ON
[/sql]

Run the above query after disconnecting all the queries connected to SampleDeadLockDatabase
Now if you run the above Dead Lock Scenario steps you will notice that the deadlock is not occurring now.

2.Create index on the table so that there will be alternates for Table Scan.
Before testing the below approach remove snapshot isolation

[sql]
ALTER DATABASE SampleDeadLockDatabase
SET READ_COMMITTED_SNAPSHOT OFF
[/sql]

Script to Create clustered indexes on tables.
[sql]
ALTER TABLE SampleTable1
ADD CONSTRAINT SampleTable1_PK PRIMARY KEY (ID)
GO

ALTER TABLE SampleTable2
ADD CONSTRAINT SampleTable2_PK PRIMARY KEY (ID)
[/sql]

Now if you run the above Dead Lock Scenario steps you will notice that the deadlock is not occurring now. But you can reproduce dead lock if you force SQL Server to do an index /table scan instead of index seek. Using WITH (INDEX (0)) hint in Step 3 and Step 4.

[sql]
–Step3 Begins–
SELECT * FROM SampleTable2 WITH (INDEX(0)) WHERE ID=4

ROLLBACK TRAN
–Step3 Ends—

–Step4 Begins–
SELECT * FROM SampleTable1 WITH (INDEX(0)) WHERE ID=3

ROLLBACK TRAN
–Step4 Ends–
[/sql]

Transaction (Process ID ) was deadlocked on resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Error Message:

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

Why This Error?
Deadlock occurs when any two SQL server process IDs are locking a seperate resource and each one of them is trying to access the resource locked by the other process.

Fix/Resolution:
Check the below link for real time scenario and resolution to deadlock:
Deadlock Internals with Real Time Scenario – Explanation and Resolution

1) Try to use SQL server profiler to Analyze Deadlocks. http://msdn.microsoft.com/en-us/library/ms188246.aspx

2) In each script that has a transaction(locking) invoived, Begin the transaction as late as possible and commit it as soon as possible.

3) Try to set the optimal lock time value depending up on your script. Link: Using @@LOCK_TIMEOUT

4) SET the DEADLOCK_PRIORITY value in each script so that the priority processes wont be deadlocked. Link: SET DEADLOCK_PRIORITY

5) Use query hints for each TSQL Query.
Use WITH NOLOCK in select statements (when the waiting/locking is not needed)


ProcessID’s: Process ID 1 State 1 Process ID 2 State 2 Process ID 3 State 3 Process ID 4 State 4 Process ID 5 State 5 Process ID 6 State 6 Process ID 7 State 7 Process ID 8 State 8 Process ID 9 State 9 Process ID 10 State 10 Process ID 11 State 11 Process ID 12 State 12 Process ID 13 State 13 Process ID 14 State 14 Process ID 15 State 15 Process ID 16 State 16 Process ID 17 State 17 Process ID 18 State 18 Process ID 19 State 19 Process ID 20 State 20 Process ID 21 State 21 Process ID 22 State 22 Process ID 23 State 23 Process ID 24 State 24 Process ID 25 State 25 Process ID 26 State 26 Process ID 27 State 27 Process ID 28 State 28 Process ID 29 State 29 Process ID 30 State 30 Process ID 31 State 31 Process ID 32 State 32 Process ID 33 State 33 Process ID 34 State 34 Process ID 35 State 35 Process ID 36 State 36 Process ID 37 State 37 Process ID 38 State 38 Process ID 39 State 39 Process ID 40 State 40 Process ID 41 State 41 Process ID 42 State 42 Process ID 43 State 43 Process ID 44 State 44 Process ID 45 State 45 Process ID 46 State 46 Process ID 47 State 47 Process ID 48 State 48 Process ID 49 State 49 Process ID 50 State 50 Process ID 51 State 51 Process ID 52 State 52 Process ID 53 State 53 Process ID 54 State 54 Process ID 55 State 55 Process ID 56 State 56 Process ID 57 State 57 Process ID 58 State 58 Process ID 59 State 59 Process ID 60 State 60 Process ID 61 State 61 Process ID 62 State 62 Process ID 63 State 63 Process ID 64 State 64 Process ID 65 State 65 Process ID 66 State 66 Process ID 67 State 67 Process ID 68 State 68 Process ID 69 State 69 Process ID 70 State 70 Process ID 71 State 71 Process ID 72 State 72 Process ID 73 State 73 Process ID 74 State 74 Process ID 75 State 75 Process ID 76 State 76 Process ID 77 State 77 Process ID 78 State 78 Process ID 79 State 79 Process ID 80 State 80 Process ID 81 State 81 Process ID 82 State 82 Process ID 83 State 83 Process ID 84 State 84 Process ID 85 State 85 Process ID 86 State 86 Process ID 87 State 87 Process ID 88 State 88 Process ID 89 State 89 Process ID 90 State 90 Process ID 91 State 91 Process ID 92 State 92 Process ID 93 State 93 Process ID 94 State 94 Process ID 95 State 95 Process ID 96 State 96 Process ID 97 State 97 Process ID 98 State 98 Process ID 99 State 99 Process ID 100 State 100 Process ID 101 State 101 Process ID 102 State 102 Process ID 103 State 103 Process ID 104 State 104 Process ID 105 State 105 Process ID 106 State 106 Process ID 107 State 107 Process ID 108 State 108 Process ID 109 State 109 Process ID 110 State 110 Process ID 111 State 111 Process ID 112 State 112 Process ID 113 State 113 Process ID 114 State 114 Process ID 115 State 115 Process ID 116 State 116 Process ID 117 State 117 Process ID 118 State 118 Process ID 119 State 119 Process ID 120 State 120 Process ID 121 State 121 Process ID 122 State 122 Process ID 123 State 123 Process ID 124 State 124 Process ID 125 State 125 Process ID 126 State 126 Process ID 127 State 127 Process ID 128 State 128 Process ID 129 State 129 Process ID 130 State 130 Process ID 131 State 131 Process ID 132 State 132 Process ID 133 State 133 Process ID 134 State 134 Process ID 135 State 135 Process ID 136 State 136 Process ID 137 State 137 Process ID 138 State 138 Process ID 139 State 139 Process ID 140 State 140 Process ID 141 State 141 Process ID 142 State 142 Process ID 143 State 143 Process ID 144 State 144 Process ID 145 State 145 Process ID 146 State 146 Process ID 147 State 147 Process ID 148 State 148 Process ID 149 State 149 Process ID 150 State 150

Applied to SQL Server 2005,SQL Server 2008,SQL Server 2008 r2,SQL Server 2012

DEADLOCK_PRIORITY

DEADLOCK_PRIORITY is used to specify the priority of a session when deadlocked by other session.
The session that has lower DEADLOCK_PRIORITY will be chosen as a victim for deadlock.

Dead Lock

Syntax:
[sql]
SET DEADLOCK_PRIORITY value
[/sql]

Value Can Be:
LOW
NORMAL
HIGH
Numeric value from -10 to 10

Example:
To set HIGH DEADLOCK_PRIORITY to a connection:
[sql]
SET DEADLOCK_PRIORITY HIGH
[/sql]
OR
[sql]
SET DEADLOCK_PRIORITY 5
[/sql]
OR
[sql]
DECLARE @A VARCHAR(10)
SET @A = ‘HIGH’
SET DEADLOCK_PRIORITY @A
[/sql]
OR
[sql]
DECLARE @A INT
SET @A = 5
SET DEADLOCK_PRIORITY @A
[/sql]

Note:
Numeric equivalent values of DEADLOCK_PRIORITY.
HIGH = 5
NORMAL = 0
LOW = -5

Applicable to SQL Server 2000,SQL Server 2005,SQL Server 2008,SQL Server 2008 r2,SQL Server 2012