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

Inv*_*con 5 .net c# 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这对于在初始查询执行期间发生死锁的情况非常有效,但现在我们在使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

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

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

Rem*_*anu 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 ();
  }
}
Run Code Online (Sandbox Code Playgroud)

您必须重试整个 DoWork通话:

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