如何使用 ADO 在 C# 中获得高效的 Sql Server 死锁处理?
Posted
技术标签:
【中文标题】如何使用 ADO 在 C# 中获得高效的 Sql Server 死锁处理?【英文标题】:How to get efficient Sql Server deadlock handling in C# with ADO? 【发布时间】:2010-09-24 03:40:51 【问题描述】:我有一个作为 ADO.net 包装器的“数据库”类。例如,当我需要执行一个过程时,我调用 Database.ExecuteProcedure(procedureName, parametersAndItsValues)。
我们在 SQL Server 2000 中遇到了死锁情况的严重问题。我们团队的一部分正在研究 sql 代码和事务以最大限度地减少这些事件,但我正在考虑使这个 Database 类对死锁情况具有鲁棒性。
我们希望死锁受害者在延迟一段时间后重试,但我不知道这是否可能。这是我们使用的方法的代码:
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 块中进行这种处理吗?
【问题讨论】:
【参考方案1】:如果可以在数据层解决死锁,那肯定是要走的路。锁定提示,重新设计模块的工作方式等等。不过,NoLock 并不是万能药——有时由于事务完整性的原因无法使用它,而且我遇到过使用 NoLock 的所有相关表读取直接(尽管很复杂)数据的情况,这仍然会导致其他查询阻塞。
无论如何——如果你不能在数据层解决它,那又如何
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;
【讨论】:
我对使用上述解决方案很感兴趣,但希望获得更多关于这对我的案例究竟如何起作用的信息。 支持随机计时器,Sams 解决方案重创 sql server 我建议最后一行应该是throw;
而不是throw exDead;
【参考方案2】:
基于@Sam 的回复,我提出了一个通用的重试包装方法:
private static T Retry<T>(Func<T> 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<T>(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;
);
【讨论】:
【参考方案3】:首先,我将查看我的 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;
【讨论】:
考虑在Thread.Sleep(100);
之后在retryCount--;
之后在重试循环中添加一个短暂的延迟,以防止对SQL Server 造成如此严重的打击。【参考方案4】:
如果遇到死锁问题,最好看看 SQL 代码在做什么。例如,如果您具有可序列化的隔离级别(或 rdbms 中的任何等效隔离级别),那么锁升级死锁很容易创建 - 并且可以通过几种方式缓解,例如重新排序查询,或者(在 SQL Server至少)使用 (UPDLOCK) 更早地获得写锁(这样你就不会获得竞争读锁)。
重试将是混合的......例如,如果您在 TransactionScope 中,它可能已经中止。但只是在纯粹主义者的层面上 - 如果我在与 db 交谈时遇到问题,我希望我的代码恐慌,并尽早恐慌......在这种特殊情况下重试似乎有点 hacky。
【讨论】:
我们正在考虑所有 sql 代码和事务,这正在完成,我们不只是试图通过在 c# 中调用来欺骗死锁。但是并发会造成死锁牺牲品,因此我们希望在发生这种情况时让我们的应用更加健壮。 Victor - 你可以而且应该通过在数据层解决死锁来处理它们。解决后,您无需在 C# 中执行任何操作。 @Dave - 这可能过于简单了 @Marc - 没错,但对于提出这类问题的人来说,99% 的时间他们来自于他们或他们在交易处理方面没有丰富经验的背景,他们试图在不止一个地方解决问题,从而使问题过于复杂。以上是关于如何使用 ADO 在 C# 中获得高效的 Sql Server 死锁处理?的主要内容,如果未能解决你的问题,请参考以下文章