【问题标题】:How to recover from deadlock exception during SqlDataReader.Read()如何在 SqlDataReader.Read() 期间从死锁异常中恢复
【发布时间】:2010-11-10 15:38:49
【问题描述】:

我的 .NET 应用程序的事件日志显示它在从 Sql Server 读取时偶尔会死锁。这通常很少见,因为我们已经优化了查询以避免死锁,但有时仍然会发生。过去,我们在SqlCommand 实例上调用ExecuteReader 函数时发生了一些死锁。为了解决这个问题,我们添加了重试代码来简单地再次运行查询,如下所示:

//try to run the query five times if a deadlock happends
int DeadLockRetry = 5;

while (DeadLockRetry > 0)
{
    try
    {
        return dbCommand.ExecuteReader();
    }
    catch (SqlException err)
    {
        //throw an exception if the error is not a deadlock or the deadlock still exists after 5 tries
        if (err.Number != 1205 || --DeadLockRetry == 0)
            throw;
    }
}

这对于在初始查询执行期间发生死锁的情况非常有效,但现在我们在使用返回的SqlDataReader 上的Read() 函数迭代结果时遇到死锁。

同样,我并不关心优化查询,而只是在发生死锁的极少数情况下尝试恢复。我正在考虑使用类似的重试过程。我可以创建自己的类,该类继承自SqlDataReader,它只是用重试代码覆盖Read 函数。像这样:

public class MyDataReader : SqlDataReader
{
    public override bool Read()
    {
        int DeadLockRetry = 5;
        while (DeadLockRetry > 0)
        {
            try
            {
                return base.Read();
            }
            catch (SqlException ex)
            {
                if (ex.ErrorCode != 1205 || --DeadLockRetry == 0)
                    throw;
            }
        }
        return false;
    }
}

这是正确的方法吗?我想确保阅读器中不会跳过记录。死锁后重试Read 会跳过任何行吗?另外,我是否应该在重试之间调用Thread.Sleep 以让数据库有时间摆脱死锁状态,或者这样就足够了。这种情况不容易重现,所以我想在修改任何代码之前确定它。

编辑:

根据要求,提供有关我的情况的更多信息:在一种情况下,我有一个执行查询的进程,该查询加载需要更新的记录 ID 列表。然后我使用Read 函数遍历该ID 列表并对该记录运行更新过程,最终将更新数据库中该记录的值。 (不,没有办法在初始查询中执行更新,返回的每条记录都会发生许多其他事情)。这段代码已经运行了一段时间,但是我们为每条记录运行了相当多的代码,所以我可以想象其中一个过程是在正在读取的初始表上创建一个锁。

经过一番思考,Scottie 提出的使用数据结构存储结果的建议可能会解决这种情况。我可以将返回的 ID 存储在 List<int> 中并循环遍历它。这样行上的锁就可以立即解除。

但是,我仍然想知道是否有一种通用的方法可以从读取死锁中恢复。

【问题讨论】:

  • 你能提供更多关于你的数据库使用的信息吗?读取数据时出现死锁很奇怪。这意味着您的代码已经为其他数据持有一些锁,并且另一个事务已锁定您正在尝试读取的内容并正在等待您之前访问过的数据。你可以在另一个事务上调用 ExecuteReader 吗?

标签: c# .net sql-server deadlock


【解决方案1】:

您的整个事务在死锁中丢失。您必须从头开始,从您读取数据阅读器的级别之上的方式开始。你说你读了一些记录,然后循环更新它们。您必须重新开始阅读记录:

function DoWork() {
  using (TransactionScope scope = new TransactionScope(...)) {
    cmd = new SqlCommand("select ...");
    using (DataReader rdr = cmd.ExecuteReader ()) {
        while(rdr.Read()) {
          ... process each record
        }
    }
    scope.Complete ();
  }
}

你必须重试整个DoWork调用:

retries = 0;
success = false;
do {
 try {
  DoWork ();
  success = true;
 }
 catch (SqlException e) {
   if (can retry e)  {
     ++retries;
   }
   else {
     throw;
   }
 }
} while (!success);

【讨论】:

  • 这个。您不能只是尝试重新阅读,您需要重新运行命令。但是您绝对应该将重试次数限制为最多 3-5 次,SQL Server 需要一些时间来处理死锁。
  • 好的,谢谢,这就是我要找的。我们有一个抽象的数据层,它为所有读取查询返回 IDataReaders,然后我们的逻辑代码使用读取器。因此,在我的应用程序中,按照您的建议对我的阅读进行编码并不容易。我希望有一个通用的解决方案,但看起来这是不可能的。我会考虑将此建议用于导致偶尔出现死锁的特定情况。谢谢大家的建议!
【解决方案2】:

您是否可以考虑使用 DataSets 或 DataTables 代替 DataReaders?

尽管我并不肯定,但我在这里担心的是引发错误的读取将完全丢弃该记录。您可能希望在受控环境中对此进行测试,您可以在其中强制出错并查看它是重新读取记录还是丢弃它。

【讨论】:

  • 我很确定使用 DataTable 或 set 会有同样的漏洞。 .NET 不会在内部使用 Read 来填充 DataTable 吗?我绝对希望能够在受控环境中进行测试。不知道到底是什么导致了死锁,所以这很困难。
  • 您不必专门测试死锁。如果您处于受控环境中,则可以关闭 SQL 服务器一秒钟并捕获该错误。这至少会告诉您该记录是被重新读取还是被丢弃。
  • 是的,DataSets使用相同的读取机制,但我想知道MS在读取时是否已经存在死锁代码?
  • 正如我在编辑中指出的那样,将结果存储在数据结构中并一次读取所有内容的想法可能在一种情况下有效(它只是返回一个 id 列表)。您指的是哪些 MS 函数可能内置了死锁处理?
  • 使用 DataAdapter 填充数据集。我不能肯定地说他们内置了死锁支持,但可能值得一试。
猜你喜欢
  • 1970-01-01
  • 2020-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-19
  • 1970-01-01
  • 2023-03-29
相关资源
最近更新 更多