如何在C#中使用ADO获得高效的Sql Server死锁处理?

我有一个类’数据库’,作为ADO.net的包装器。 例如,当我需要执行一个过程时,我调用Database.ExecuteProcedure(procedureName,parametersAndItsValues)。

我们遇到了SQL Server 2000中死锁情况的严重问题。我们团队的一部分正在处理sql代码和事务以最小化这些事件,但我正在考虑使这个数据库类能够抵御死锁情况。

我们希望死锁受害者可能在经过一段时间的延迟后重试,但我不知道是否可能。 以下是我们使用的方法的代码:

public int ExecuteQuery(string query) { int rows = 0; try { Command.Connection = Connection; Command.CommandType = CommandType.Text; if(DatabaseType != enumDatabaseType.ORACLE) Command.CommandText = query; else Command.CommandText ="BEGIN " + query + " END;"; if (DatabaseType != enumDatabaseType.SQLCOMPACT) Command.CommandTimeout = Connection.ConnectionTimeout; if (Connection.State == ConnectionState.Closed) Connection.Open(); rows = Command.ExecuteNonQuery(); } catch (Exception exp) { //Could I add here any code to handle it? throw new Exception(exp.Message); } finally { if (Command.Transaction == null) { Connection.Close(); _connection.Dispose(); _connection = null; Command.Dispose(); Command = null; } } return rows; } 

我可以在catch块内执行此操作吗?

首先,我将查看我的SQL 2000代码并深入了解发生此死锁的原因。 修复此问题可能会隐藏更大的问题(例如,丢失索引或错误查询)。

其次,我会检查我的架构,以确认经常需要调用死锁语句select count(*) from bob是否必须每秒调用100次?)。

但是,如果您确实需要一些死锁支持并且在SQL或体系结构中没有错误,请尝试以下几行。 (注意:我必须将此技术用于每秒支持数千个查询的系统,并且很少会遇到死锁)

 int retryCount = 3; bool success = false; while (retryCount > 0 && !success) { try { // your sql here success = true; } catch (SqlException exception) { if (exception.Number != 1205) { // a sql exception that is not a deadlock throw; } // Add delay here if you wish. retryCount--; if (retryCount == 0) throw; } } 

在@Sam的响应的基础上,我提出了一个通用的重试包装方法:

 private static T Retry(Func func) { int count = 3; TimeSpan delay = TimeSpan.FromSeconds(5); while (true) { try { return func(); } catch(SqlException e) { --count; if (count <= 0) throw; if (e.Number == 1205) _log.Debug("Deadlock, retrying", e); else if (e.Number == -2) _log.Debug("Timeout, retrying", e); else throw; Thread.Sleep(delay); } } } private static void Retry(Action action) { Retry(() => { action(); return true; }); } // Example usage protected static void Execute(string connectionString, string commandString) { _log.DebugFormat("SQL Execute \"{0}\" on {1}", commandString, connectionString); Retry(() => { using (SqlConnection connection = new SqlConnection(connectionString)) using (SqlCommand command = new SqlCommand(commandString, connection)) command.ExecuteNonQuery(); }); } protected static T GetValue(string connectionString, string commandString) { _log.DebugFormat("SQL Scalar Query \"{0}\" on {1}", commandString, connectionString); return Retry(() => { using (SqlConnection connection = new SqlConnection(connectionString)) using (SqlCommand command = new SqlCommand(commandString, connection)) { object value = command.ExecuteScalar(); if (value is DBNull) return default(T); return (T) value; } }); } 

如果可以在数据层解决死锁,那肯定是要走的路。 锁定提示,重新设计模块的工作方式等等。 NoLock不是灵丹妙药 – 有时由于事务完整性的原因而无法使用,我有一些直接(尽管很复杂)数据读取的情况,所有相关表NoLock’d仍会导致其他查询的阻塞。

无论如何 – 如果由于某种原因你无法在数据层解决它,那怎么样

 bool OK = false; Random Rnd = new Random(); while(!OK) { try { rows = Command.ExecuteNonQuery(); OK = true; } catch(Exception exDead) { if(exDead.Message.ToLower().Contains("deadlock")) System.Threading.Thread.Sleep(Rnd.Next(1000, 5000)); else throw exDead; } } 

如果遇到死锁问题,最好查看SQL代码在做什么。 例如,如果您具有可序列化的隔离级别(或rdbms中的等效项),则很容易创建锁定升级死锁 – 并且可以通过几种方式进行缓解,例如重新排序查询,或者(在SQL Server中)至少使用(UPDLOCK)更早地进行写锁定(因此您不会获得竞争读锁)。

重新尝试将会混合…例如,如果您在TransactionScope中,它可能已经中止。 但只是在纯粹层面 – 如果我在与数据库谈话时遇到问题我希望我的代码恐慌,并且恐慌早期……在这种特殊情况下重新尝试似乎有点笨拙。