Admin posted on September 22, 2009 17:57

Are Deadlocks becoming a thing of the past?

 

Unless I missed it, I didn’t see his example

There was a blog post [1] awhile back by super-stealth DBA dude Brad McGehee that described the notion of using the new Try/Catch syntax for handling deadlocks. Great idea and good blog posting! The only problem that I found, however, was that he never gave an example that actually showed the benefit of using such. So I thought that I’d go ahead and create a scenario that shows a deadlock, and then use the feature that he mentions to handle one. After I wrote mine, I peeked in Books Online (BOL) to find that they did a somewhat similar example, and per usual, I liked mine better than theirs; easier, simpler, and more straightforward IMHO, so I hope you find same. Let’s look at the code.


Set up the tables and data

Run the following in SQL Server Management Studio (SSMS)

 

DROP TABLE ##test1, ##test2
go
CREATE TABLE ##test1 (id int PRIMARY KEY CLUSTERED)
GO
CREATE TABLE ##test2 (id int PRIMARY KEY CLUSTERED)
GO
INSERT INTO ##test1 VALUES (1)
INSERT INTO ##test2 VALUES (1)
GO

 

Instructions next up. Please follow carefully or suffer severe flogging

Here are the very simple and easy-to-understand instructions for all of the below code examples. Please read them – the order of execution is important, and if you don’t execute them in this order using the particular window as directed, the demos won’t work for you. When doing our own testing, either commit or rollback transactions often, or the code also won’t work.

  • Run SCRIPT 1 in an SSMS window
  • Run SCRIPT 2 in another SSMS window
  • Run SCRIPT 3 back in the window that you ran in step (1)

 

Lunch is served at 1205 today

The example, a variation from WardyIT’s blog [2], is an example of a “Hold and Wait” type of deadlock, where one process is waiting on another while that process attempts to gain access to a held process. Run example 1 (using the instructions given) and you should get a 1205 deadlock error. Remember – Script 1-> window 1, script 2 –> window 2, script 3 –> window 1.  Awesome! Great job.

 

--EXAMPLE 1
--SCRIPT 1 in window 1

BEGIN TRAN
    UPDATE ##test1 SET id = 1

--SCRIPT 2 in window 2
BEGIN TRAN
    UPDATE ##test2 SET id = 2
    UPDATE ##test1 SET id = 2
    
--SCRIPT 3 in window 1
BEGIN TRAN
    UPDATE ##test2 SET id = 1

 

Msg 1205, Level 13, State 51, Line 3

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

 

TRY/CATCH is catching!

Thank goodness for TRY/CATCH in TSQL; we now have a facility to handle errors thoroughly. Run Example 2 now in order to see that this logic does not work with the old syntax; I have deliberately commented the code to simple BEGIN/END blocks. You’ll notice that SQL Server punts when it receives an error, and what is happening is that the constructs cannot trap errors and essentially no exception handling is occurring.

 

--EXAMPLE 2
--SCRIPT 1
BEGIN TRAN
    UPDATE ##test1 SET id = 1

--SCRIPT 2
DECLARE @err int

BEGIN TRAN
Retry:
    IF (@@TRANCOUNT=0)
        BEGIN TRAN
        
    BEGIN --TRY                
        UPDATE ##test2
        SET id = 2

        UPDATE ##test1
        SET id = 2
     END --TRY
     
    BEGIN --CATCH
        SET @err = @@ERROR
     
        IF (@err = 1205)
        BEGIN
            PRINT 'RETRY OCCURRED!'
            ROLLBACK
            SET @err = 0
            GOTO Retry
        END
    END --CATCH    
COMMIT    

--SCRIPT 3
    
DECLARE @err int

BEGIN TRAN
    Retry:
        IF (@@TRANCOUNT=0)
            BEGIN TRAN
            
        BEGIN --TRY    
            UPDATE ##test2
            SET id = 1
         END --TRY
         
        BEGIN --CATCH
            SET @err = @@ERROR
         
            IF (@err = 1205)
            BEGIN
                PRINT 'RETRY OCCURRED!'
                ROLLBACK
                SET @err = 0
                GOTO Retry
            END    
        END --CATCH
COMMIT 


Deadlocks – a dying breed

Run example three now and check out the results. Notice that I use a GOTO. If you don’t like one, don’t use one. For me, though, this made sense. You’ll see that BOL created a cheesy loop for their transactions…think I’ll stay clear of all that, plus mine is shorter anyway. BOL also says that “GOTO statements cannot be used to enter a TRY or CATCH block”, but I noticed that it didn’t say a darn thing about exiting them, which is exactly what we do here.

--EXAMPLE 3
--SCRIPT 1
    BEGIN TRAN
    UPDATE ##test1 SET id = 1 

--SCRIPT 2
DECLARE @err int

BEGIN TRAN
    Retry:
        IF (@@TRANCOUNT=0)
            BEGIN TRAN
            
        BEGIN TRY                
            UPDATE ##test2
            SET id = 2

            UPDATE ##test1
            SET id = 2
         END TRY
         
         BEGIN CATCH
            SET @err = ERROR_NUMBER()
         
            IF (@err = 1205)
            BEGIN
                PRINT 'RETRY OCCURRED!'
                ROLLBACK
                SET @err = 0
                GOTO Retry
            END
        END CATCH    
COMMIT    
    

--SCRIPT 3    
DECLARE @err int

BEGIN TRAN
    Retry:
        IF (@@TRANCOUNT=0)
            BEGIN TRAN
            
        BEGIN TRY    
            UPDATE ##test2
            SET id = 1
         END TRY
         
         BEGIN CATCH
            SET @err = ERROR_NUMBER()
         
            IF (@err = 1205)
            BEGIN
                PRINT 'RETRY OCCURRED!'
                ROLLBACK
                SET @err = 0
                GOTO Retry
            END    
        END CATCH
COMMIT    

Conclusion. Did you catch all of this, there buddy, or did you even try?

Deadlocks can be handled a number of different ways. There are four different types of deadlocks, and we looked at and resolved a common “Hold and Wait” type in this blog posting. Here I showed a code snippet that created a deadlock, and then another one that took the same code and solved the deadlock problem using a retry label for a GOTO statement and a TRY/CATCH block to handle the error. In previous versions of SQL Server you couldn’t “trap” the error – SQL Server simply raised an internal error before your code could kick in, and one or more of the threads became a deadlock victim. The beauty of TRY/CATCH is that these constructs serve as a true mechanism for error handling, and allow the SQL dev to create robust code for trapping and handling errors.

What I did here was a “proof of concept”  in tech speak – I am not at a client who is at this point suffering from deadlocks, so I cannot fully test this code. However, give this concept a shot if you’re dying from the dreaded deadlock; some variation of this might help you out.

Lee Everest

----------------------------

No, deadlocks are not a popular hairstyle of many NFL and NCAA football players.


EM Banana Rastafarian

 

 

 

[1]. McGehee, B. (2005). Using TRY/CATCH to Resolve a Deadlock in SQL Server 2005.  Retrieved on 9/22/2009 from http://www.sql-server-performance.com/articles/per/deadlock_sql_2005_p1.aspx

[2]. WardyIT (2005). Creating a deadlock. Retrieved on 9/22/09 from http://wardyit.com/blog//blog/archive/2005/12/12/65.aspx 

 


Posted in: SQLServerPedia  Tags:
blog comments powered by Disqus

by Lee Everest, M.S.

Poll

Do you use Azure or cloud in your organization?



Show Results

Ads

Search


Month List

Calendar

«  February 2012  »
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011
View posts in large calendar

Tags

Disclaimer
The opinions, code, examples, et.al. expressed herein are my own personal opinions and do not represent my employer's view in any way, shape form, or fashion.  All code for demonstration purposes - no guarantees, either written or implied, are made.

© Copyright 2012 Lee Everest's SQL Server, etc. weblog