代码之家  ›  专栏  ›  技术社区  ›  InvisibleBacon

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

  •  5
  • InvisibleBacon  · 技术社区  · 14 年前

    我的.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

    同样,我不关心优化查询,只是在极少发生死锁的情况下尝试恢复。我在考虑使用类似的重试过程。我可以创建继承自 数据阅读器 它简单地覆盖了 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;
        }
    }
    

    这是正确的方法吗?我想确保读者不会跳过记录。将重试 死锁后跳过任何行?另外,我应该打电话吗 Thread.Sleep 在两次重试之间给数据库时间以摆脱死锁状态,还是这就足够了。这个例子不容易复制,所以我想在修改任何代码之前先确认一下。

    编辑:

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

    经过思考,Scottie使用数据结构来存储结果的建议可能会解决这种情况。我可以将返回的ID存储在 List<int> 然后循环。这样就可以立即移除行上的锁。

    然而,我仍然有兴趣知道是否有一种通用的方法可以从读操作的死锁中恢复过来。

    2 回复  |  直到 14 年前
        1
  •  4
  •   Remus Rusanu    14 年前

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

    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);
    
        2
  •  0
  •   Scottie    14 年前

    您是否可以考虑使用数据集或数据表而不是DataReader?

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