如何在SqlDataReader.Read()期间从死锁异常中恢复

时间:2010-11-10 15:38:49

标签: c# .net sql-server deadlock

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

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

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

同样,我并不关心优化查询,而只是尝试在极少数情况下恢复发生死锁。我在考虑使用类似的重试过程。我可以创建自己的继承自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建议使用数据结构存储结果可能会解决这种情况。我可以存储在List<int>中返回的id并循环遍历它。这样就可以立即删除行上的锁。

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

2 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:0)

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

我在这里的恐惧,虽然我不是肯定的,但是抛出错误的读取将完全抛弃该记录。您可能希望在受控环境中对此进行测试,您可以在该环境中强制发生错误并查看是否重新读取记录或是否只丢弃该记录。