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

我的.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()函数迭代结果时遇到死锁。

同样,我并不关心优化查询,而只是尝试在极少数情况下恢复发生死锁。 我在考虑使用类似的重试过程。 我可以创建自己的inheritance自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返回的id并循环遍历它。 这样就可以立即删除行上的锁。

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

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

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

您是否可以考虑使用DataSet或DataTables而不是DataReaders?

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