23

我有一个作为 ADO.net 包装器的“数据库”类。例如,当我需要执行一个过程时,我调用 Database.ExecuteProcedure(procedureName, parametersAndItsValues)。

我们在 SQL Server 2000 中遇到了死锁情况的严重问题。我们团队的一部分正在研究 sql 代码和事务以尽量减少这些事件,但我正在考虑使这个数据库类对死锁情况具有鲁棒性。

我们希望死锁受害者在延迟一段时间后重试,但我不知道这是否可能。这是我们使用的方法的代码:

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;
}

我可以在 catch 块内进行此处理吗?

4

4 回答 4

40

首先,我将查看我的 SQL 2000 代码并了解为什么会发生这种死锁。解决这个问题可能会隐藏一个更大的问题(例如,缺少索引或错误的查询)。

其次,我会检查我的架构,以确认确实需要频繁调用死锁语句(是否select count(*) from bob必须每秒调用 100 次?)。

但是,如果您确实需要一些死锁支持并且在您的 SQL 或体系结构中没有错误,请尝试以下几行。(注意:我不得不将这种技术用于每秒支持数千个查询的系统,并且很少会遇到死锁)

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--; 
     if (retryCount == 0) throw;
  }
}
于 2008-12-02T22:18:49.663 回答
24

基于@Sam 的回复,我提出了一个通用的重试包装方法:

private static T Retry<T>(Func<T> func)
{
    int count = 3;
    TimeSpan delay = TimeSpan.FromSeconds(5);
    while (true)
    {
        try
        {
            return func();
        }
        catch(SqlException e)
        {
            --count;
            if (count <= 0) throw;

            if (e.Number == 1205)
                _log.Debug("Deadlock, retrying", e);
            else if (e.Number == -2)
                _log.Debug("Timeout, retrying", e);
            else
                throw;

            Thread.Sleep(delay);
        }
    }
}

private static void Retry(Action action)
{
    Retry(() => { action(); return true; });
}

// Example usage
protected static void Execute(string connectionString, string commandString)
{
    _log.DebugFormat("SQL Execute \"{0}\" on {1}", commandString, connectionString);

    Retry(() => {
        using (SqlConnection connection = new SqlConnection(connectionString))
        using (SqlCommand command = new SqlCommand(commandString, connection))
            command.ExecuteNonQuery();
    });
}

protected static T GetValue<T>(string connectionString, string commandString)
{
    _log.DebugFormat("SQL Scalar Query \"{0}\" on {1}", commandString, connectionString);

    return Retry(() => { 
        using (SqlConnection connection = new SqlConnection(connectionString))
        using (SqlCommand command = new SqlCommand(commandString, connection))
        {
            object value = command.ExecuteScalar();
            if (value is DBNull) return default(T);
            return (T) value;
        }
    });
}
于 2011-07-14T11:13:50.490 回答
5

如果可以在数据层解决死锁,那肯定是要走的路。锁定提示,重新设计模块的工作方式等等。不过,NoLock 并不是万能药——有时由于事务完整性的原因无法使用,而且我曾遇到过使用 NoLock 的所有相关表读取直接(尽管很复杂)数据的情况,这些表仍然会导致其他查询阻塞。

无论如何-如果您出于某种原因无法在数据层解决它,那怎么样

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;
    }
}
于 2008-12-02T10:22:03.807 回答
4

如果遇到死锁问题,最好看看 SQL 代码在做什么。例如,如果您具有可序列化的隔离级别(或 rdbms 中的任何等效级别),那么锁升级死锁很容易创建 - 并且可以通过几种方式缓解,例如重新排序查询,或者(在 SQL Server至少)使用(UPDLOCK)更早地获得写锁(这样你就不会获得竞争读锁)。

重试将是混合的......例如,如果您在 TransactionScope 中,它可能已经中止。但就在纯粹主义者的层面上——如果我在与 db 交谈时遇到问题,我希望我的代码恐慌,并尽早恐慌......在这种特殊情况下,重试似乎有点 hacky。

于 2008-11-26T13:15:20.790 回答