【问题标题】:How to get efficient Sql Server deadlock handling in C# with ADO?如何使用 ADO 在 C# 中获得高效的 Sql Server 死锁处理?
【发布时间】:2010-09-24 03:40:51
【问题描述】:

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

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

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

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 块中进行这种处理吗?

【问题讨论】:

    标签: c# sql-server ado.net .net-2.0 deadlock


    【解决方案1】:

    如果可以在数据层解决死锁,那肯定是要走的路。锁定提示,重新设计模块的工作方式等等。不过,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;
        }
    }
    

    【讨论】:

    • 我对使用上述解决方案很感兴趣,但希望获得更多关于这对我的案例究竟如何起作用的信息。
    • 支持随机计时器,Sams 解决方案重创 sql server
    • 我建议最后一行应该是throw; 而不是throw exDead;
    【解决方案2】:

    基于@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;
            }
        });
    }
    

    【讨论】:

      【解决方案3】:

      首先,我将查看我的 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;
        }
      }
      

      【讨论】:

      • 考虑在Thread.Sleep(100); 之后在retryCount--; 之后在重试循环中添加一个短暂的延迟,以防止对SQL Server 造成如此严重的打击。
      【解决方案4】:

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

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

      【讨论】:

      • 我们正在考虑所有 sql 代码和事务,这正在完成,我们不只是试图通过在 c# 中调用来欺骗死锁。但是并发会造成死锁牺牲品,因此我们希望在发生这种情况时让我们的应用更加健壮。
      • Victor - 你可以而且应该通过在数据层解决死锁来处理它们。解决后,您无需在 C# 中执行任何操作。
      • @Dave - 这可能过于简单了
      • @Marc - 没错,但对于提出这类问题的人来说,99% 的时间他们来自于他们或他们在交易处理方面没有丰富经验的背景,他们试图在不止一个地方解决问题,从而使问题过于复杂。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多