什么是用于捕获SQLException和重试的优秀C#编码样式

我有一个方法调用SQLServer函数对表执行自由文本搜索。 该函数有时会在第一次调用时产生一个SQLException:“对于全文查询字符串断字超时”。 所以我通常想重试该请求,因为它会在后续请求中成功。 构造重试逻辑的好方法是什么。 目前我有以下内容:

var retryCount = 0; var results = new List(); using (var ctx = new UsersDataContext(ConfigurationManager.ConnectionStrings[CONNECTION_STRING_KEY].ConnectionString)) { for (; ; ) { try { results = ctx.SearchPhoneList(value, maxRows) .Select(user => user.ToDto()) .ToList(); break; } catch (SqlException) { retryCount++; if (retryCount > MAX_RETRY) throw; } } } return results; 

我将exception处理更改为仅重试某些错误:

  • 1204,1205死锁
  • -2超时
  • -1连接坏了

这些是基本的“可重试”错误

 catch (SqlException ex) { if !(ex.Number == 1205 || ex.Number == 1204 || ... ) { throw } retryCount++; if (retryCount > MAX_RETRY) throw; } 

编辑,我清理忘了等待所以你不要锤击SQL框:

  • 在死锁上添加500毫秒等待
  • 超时延迟增加5秒

编辑2:

我是开发人员DBA,不做太多C#。 我的答案是纠正调用的exception处理…

感谢所有的反馈。 我自己回答这个问题,所以我可以从给出的答案中加入元素。 如果我错过了什么,请告诉我。 我的方法变成:

 var results = new List(); Retry(ctx => results = ctx.SearchPhoneList(value, maxRows) .Select(user => user.ToDto()) .ToList()); return results; 

我重构了原始的重用方法。 还有很多层次的筑巢。 它还依赖于数据上下文的默认构造函数,这可能限制性太强。 @Martin,我考虑过包括你的PreserveStackTrace方法,但在这种情况下我认为它确实没有增加足够的价值 – 很高兴知道以备参考感谢:

 private const int MAX_RETRY = 2; private const double LONG_WAIT_SECONDS = 5; private const double SHORT_WAIT_SECONDS = 0.5; private static readonly TimeSpan longWait = TimeSpan.FromSeconds(LONG_WAIT_SECONDS); private static readonly TimeSpan shortWait = TimeSpan.FromSeconds(SHORT_WAIT_SECONDS); private enum RetryableSqlErrors { Timeout = -2, NoLock = 1204, Deadlock = 1205, WordbreakerTimeout = 30053, } private void Retry(Action retryAction) where T : DataContext, new() { var retryCount = 0; using (var ctx = new T()) { for (;;) { try { retryAction(ctx); break; } catch (SqlException ex) when (ex.Number == (int) RetryableSqlErrors.Timeout && retryCount < MAX_RETRY) { Thread.Sleep(longWait); } catch (SqlException ex) when (Enum.IsDefined(typeof(RetryableSqlErrors), ex.Number) && retryCount < MAX_RETRY) { Thread.Sleep(shortWait); } retryCount++; } } } 

我对sql的retryables的枚举看起来像这样:

 SqlConnectionBroken = -1, SqlTimeout = -2, SqlOutOfMemory = 701, SqlOutOfLocks = 1204, SqlDeadlockVictim = 1205, SqlLockRequestTimeout = 1222, SqlTimeoutWaitingForMemoryResource = 8645, SqlLowMemoryCondition = 8651, SqlWordbreakerTimeout = 30053 

这不是好的风格,但有时你必须这样做,因为你根本无法改变现有的代码并且必须处理它。

我在这种情况下使用以下通用方法。 注意PreserveStackTrace()方法,它有时在重新抛出场景中非常有用。

 public static void RetryBeforeThrow(Action action, int retries, int timeout) where T : Exception { if (action == null) throw new ArgumentNullException("action", string.Format("Argument '{0}' cannot be null.", "action")); int tries = 1; do { try { action(); return; } catch (T ex) { if (retries <= 0) { PreserveStackTrace(ex); throw; } Thread.Sleep(timeout); } } while (tries++ < retries); } ///  /// Sets a flag on an  so that all the stack trace information is preserved /// when the exception is re-thrown. ///  /// This is useful because "throw" removes information, such as the original stack frame. ///  public static void PreserveStackTrace(Exception ex) { MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); preserveStackTrace.Invoke(ex, null); } 

你会这样称呼它:

 RetryBeforeThrow(() => MethodWhichFails(), 3, 100); 

做这样的事情没有好的风格。 你最好弄清楚为什么请求第一次失败但第二次成功。

似乎Sql Server可能最初编译执行计划然后执行查询。 因此,第一次调用失败,因为组合时间超过了超时属性,并且第二次成功,因为已经编译并保存了执行计划。

我不知道UsersDataContext是如何工作的,但可能是您可以选择在实际执行之前Prepare查询。

真正的答案:如果我必须这样做,我会重试一次而不是再次,像这样:

 var results = new List(); using (var ctx = new UsersDataContext(ConfigurationManager.ConnectionStrings[CONNECTION_STRING_KEY].ConnectionString)) { try { results = ctx.SearchPhoneList(value, maxRows) .Select(user => user.ToDto()) .ToList(); break; } catch (SqlException) { try { results = ctx.SearchPhoneList(value, maxRows) .Select(user => user.ToDto()) .ToList(); break; } catch (SqlException) { // set return value, or indicate failure to user however } } } } return results; 

虽然我可能会相信不会滥用重试过程,但你会诱使你的继任者增加重试次数作为快速修复。

我认为使用指定重试计数的方面的注释方法将导致更多结构化代码,尽管需要一些基础结构编码

您可以简单地使用SqlConnectionStringBuilder属性来进行sql连接重试。

var conBuilder = new SqlConnectionStringBuilder("Server=.;Database=xxxx;Trusted_Connection=True;MultipleActiveResultSets=true"); conBuilder.ConnectTimeout = 90; conBuilder.ConnectRetryInterval = 15; conBuilder.ConnectRetryCount = 6;

注意: – 必需.Net 4.5或更高版本。

将相关代码拉出到自己的方法中,然后使用递归。

伪代码:

 try { doDatabaseCall(); } catch (exception e) { //Check exception object to confirm its the error you've been experiencing as opposed to the server being offline. doDatabaseCall(); }