views:

3692

answers:

3

I have a class 'Database' that works as a wrapper for ADO.net. For instance, when I need to execute a procedure, I call Database.ExecuteProcedure(procedureName, parametersAndItsValues).

We are experiencing serious problems with Deadlock situations in SQL Server 2000. A part of our team is working on the sql code and transactions to minimize these events, but I'm thinking about making this Database class robust against deadlock situations.

I wish the deadlock victim could be able to redo its locked actions, try again after some time, but I don't know if it is possible. There is the code for a method we use:

public int ExecuteQuery(string query)
{
    int rows = 0;

    try
    {
        Command.Connection = Connection;
        Command.CommandType = CommandType.Text;

        if(DatabaseType != enumDatabaseType.ORACLE)
          Command.CommandText = query;
        else
          Command.CommandText ="BEGIN " +  query + " END;";



        if (DatabaseType != enumDatabaseType.SQLCOMPACT)
            Command.CommandTimeout = Connection.ConnectionTimeout;

        if (Connection.State == ConnectionState.Closed)
            Connection.Open();

        rows = Command.ExecuteNonQuery();
    }
    catch (Exception exp)
    {
        //Could I add here any code to handle it?
        throw new Exception(exp.Message);
    }
    finally
    {
        if (Command.Transaction == null)
        {
            Connection.Close();
            _connection.Dispose();
            _connection = null;
            Command.Dispose();
            Command = null;
        }
    }
    return rows;
}

Can I do this handling inside a catch block?

A: 

If you are getting problems with deadlocks, it would be better to look at what the SQL code is doing. For example, lock-escalation deadlocks are very easy to create if you have serializable isolation level (or whatever the equivalent is in your rdbms) - and can be mitigated in a few ways, such as re-ordering queries, or (in SQL Server at least) using the (UPDLOCK) to take a write lock earlier (so you don't get a competing read-lock).

Re-trying is going to be mixed... for example, if you are in a TransactionScope, it might already have aborted. But just at the purist level - if I get problems talking to the db I want my code to panic, and panic early... re-try seems a bit hacky in this particular scenario.

Marc Gravell
We are considering all sql code and transactions, this is being done, we are not trying only to cheat deadlock by recalling at c#. But concurrency can create deadlock victims, and because of this we want to make our apps more robust when it happens.
Victor Rodrigues
Victor - you can and should deal withbthe deadlocks by solving them on the data layer. Once solved, you need not do anything at all in C#.
Dave Markle
@Dave - that is arguably overly simplistic
Marc Gravell
@Marc - True, but for people asking these kinds of questions, 99% of the time they're coming from a background where they or their don't have a lot of experience in transaction processing, and they overcomplicate the problem by trying to solve it in more than 1 place.
Dave Markle
A: 

If the deadlock can be solved at the data layer, that's definitely the way to go. Locking hints, redesigning the way the module works and so on. NoLock isn't a panacea though - sometimes it's not possible to use for reasons of transactional integrity and I have had cases of straight (albeit complex) data reads with all relevant tables NoLock'd that still caused blocks on other queries.

Anyway - if you can't solve it at the data layer for whatever reason, how about

bool OK = false;
Random Rnd = new Random();

while(!OK)
{
    try
    {
     rows = Command.ExecuteNonQuery();
     OK = true;
    }
    catch(Exception exDead)
    {
     if(exDead.Message.ToLower().Contains("deadlock"))
      System.Threading.Thread.Sleep(Rnd.Next(1000, 5000));
     else
      throw exDead;
    }
}
I am interested in using the above solution but would appreciate more information on how exactly this could work for my case.
Kobojunkie
+2  A: 

First, I would review my SQL 2000 code and get to the bottom of why this deadlock is happening. Fixing this may be hiding a bigger problem (Eg. missing index or bad query).

Second I would review my architecture to confirm the deadlocking statement really needs to be called that frequently (Does select count(*) from bob have to be called 100 times a second?).

However, if you really need some deadlock support and have no errors in your SQL or architecture try something along the following lines. (Note: I have had to use this technique for a system supporting thousands of queries per second and would hit deadlocks quite rarely)

int retryCount = 3;
bool success = false;  
while (retryCount > 0 && !success) 
{
  try
  {
     // your sql here
     success = true; 
  } 
  catch (SqlException exception)
  {
     if (exception.Number != 1205)
     {
       // a sql exception that is not a deadlock 
       throw; 
     }
     // Add delay here if you wish. 
     retryCount--; 
  }
}
Sam Saffron
Why would you use a SqlException.Number 0x4b5 instead of 1205?
Rick Glos
Well I changed it, grabbed the code off reflector in and old dll I had, in real life I would use a constant and no magic numbers
Sam Saffron