2011-01-27 18 views
16

Tôi có một phương thức gọi một hàm SQLServer để thực hiện tìm kiếm văn bản miễn phí đối với một bảng. Chức năng đó đôi khi sẽ có kết quả cuộc gọi đầu tiên trong một SQLException: "Lời vi phạm thời gian chờ cho chuỗi truy vấn toàn văn". Vì vậy, thông thường tôi muốn thử lại yêu cầu đó bởi vì nó sẽ thành công trên các yêu cầu tiếp theo. Phong cách tốt để cấu trúc logic thử lại là gì. Tại thời điểm này tôi có như sau:Phong cách mã hóa C# tốt để bắt SQLException và thử lại là gì

var retryCount = 0; 
var results = new List<UserSummaryDto>(); 
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; 
+1

Thực ra, khái niệm đơn giản và nên hoạt động. Mặc dù tôi thận trọng chống lại việc sử dụng exteme của Var. Đôi khi một int chỉ là một int. – MAW74656

+0

'Ngày Lỗi Tiếp tục Tiếp theo' trong Visual Basic là khái niệm đơn giản - điều đó không làm cho nó là một ý tưởng tốt. – MusiGenesis

+0

@ MAW74656: Tôi đã bình chọn bình luận của bạn dựa trên nhận xét 'var'. IMO, trình biên dịch sẽ không cho phép sử dụng nó khi nó che dấu loại đối tượng, như ở đây. – MusiGenesis

Trả lời

12

Cảm ơn tất cả các phản hồi. Tôi trả lời bản thân mình để tôi có thể kết hợp các yếu tố từ các câu trả lời được đưa ra. Xin vui lòng cho tôi biết nếu tôi đã bỏ lỡ một cái gì đó. Phương pháp của tôi trở thành:

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

Và tôi đã tái cấu trúc phương pháp gốc để sử dụng lại. Vẫn còn rất nhiều cấp độ làm tổ. Nó cũng dựa vào việc có một hàm tạo mặc định cho ngữ cảnh dữ liệu có thể quá hạn chế. @ Martin, tôi coi đó PreserveStackTrace phương pháp của bạn, nhưng trong trường hợp này tôi không nghĩ rằng nó thực sự bổ sung đủ giá trị - tốt để biết về tương lai tham khảo thanks:

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<T>(Action<T> 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++; 
     } 
    } 
} 
17

Tôi muốn thay đổi ngoại lệ xử lý để chỉ thử trên một số lỗi:

  • 1204, 1205 deadlocks
  • -2 timeout
  • -1 kết nối bị hỏng

Đây là những lỗi cơ bản "có thể thử lại"

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

Edit, tôi sạch sẽ quên chờ đợi do đó bạn không búa hộp SQL:

  • Thêm 500 ms chờ vào bế tắc
  • Thêm một sự chậm trễ 5 giây khi quá hạn

Chỉnh sửa 2:

Tôi là nhà phát triển DBA, không làm được nhiều C#. Câu trả lời của tôi là sửa lỗi xử lý ngoại lệ cho các cuộc gọi ...

+0

http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.2 –

+0

@jani: Liên kết của bạn có thể cải thiện chất lượng của C# (Tôi là nhà phát triển DBA: C# không phải là cốt lõi kỹ năng cho tôi) nhưng nó không giúp đỡ với bẫy các lỗi cơ sở dữ liệu độc đáo để làm việc ra nếu thử lại là có thể. – gbn

+1

Điều này là đúng cho mục đích. Một điều tôi muốn làm là nhóm các mã lỗi trong một mảng và sử dụng .Contains (ex.Number); làm cho nó nhỏ gọn hơn một chút. – KeithS

6

Phong cách không tốt, nhưng đôi khi bạn phải làm, bởi vì bạn không thể thay đổi mã hiện tại và phải xử lý nó.

Tôi đang sử dụng phương pháp chung sau đây cho trường hợp này. Lưu ý phương pháp PreserveStackTrace(), đôi khi có thể rất hữu ích trong trường hợp phát lại.

public static void RetryBeforeThrow<T>(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); 
} 

/// <summary> 
/// Sets a flag on an <see cref="T:System.Exception"/> so that all the stack trace information is preserved 
/// when the exception is re-thrown. 
/// </summary> 
/// <remarks>This is useful because "throw" removes information, such as the original stack frame.</remarks> 
/// <see href="http://weblogs.asp.net/fmarguerie/archive/2008/01/02/rethrowing-exceptions-and-preserving-the-full-call-stack-trace.aspx"/> 
public static void PreserveStackTrace(Exception ex) 
{ 
    MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); 
    preserveStackTrace.Invoke(ex, null); 
} 

Bạn sẽ gọi nó là như thế:

RetryBeforeThrow<SqlException>(() => MethodWhichFails(), 3, 100); 
+0

'throw' không được xóa khung ngăn xếp gốc. Chỉ khi bạn sử dụng 'ném ex' –

+1

@ František Žiačik Generaly nó nên. Đọc bài viết trên blog Fabrice trong câu trả lời của tôi và bạn sẽ thấy những gì tôi có ý nghĩa. –

+3

Tôi đã thử nó bản thân mình và nó quay ra bạn đúng. Người ta không bao giờ ngừng học hỏi. Cách khác để bảo vệ thông tin là bọc ngoại lệ ('ném ngoại lệ mới (" Thông điệp ", cũ)') –

4

Không có phong cách tốt để làm một cái gì đó như thế này. Bạn sẽ muốn tìm hiểu rõ hơn về số tại sao yêu cầu không thành công lần đầu tiên nhưng thành công lần thứ hai.

Có vẻ như Sql Server phải biên dịch ban đầu kế hoạch thực hiện và sau đó thực hiện truy vấn. Vì vậy, cuộc gọi đầu tiên không thành công vì thời gian kết hợp vượt quá thời gian chờ của bạn và thành công lần thứ hai vì kế hoạch thực hiện đã được biên dịch và lưu.

Tôi không biết cách UsersDataContext hoạt động, nhưng có thể là trường hợp bạn có tùy chọn truy vấn Prepare trước khi thực sự thực hiện nó.

Bất trả lời: Nếu tôi có để làm điều này, tôi sẽ thử lại chỉ một lần và không một lần nữa, như thế này:

var results = new List<UserSummaryDto>(); 
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; 

Trong khi tôi có thể tin tưởng bạn để không lạm dụng quá trình thử lại, bạn 'd được hấp dẫn người kế nhiệm của bạn để tăng số lượng thử lại như một sửa chữa nhanh chóng.

+1

Tôi định nói điều này, nhưng đã quá gà. Nếu timeouts là một vấn đề lớn, và đưa hệ thống đến nơi nó có thể đứng lên mà không cần phải làm lại, tôi sẽ xem xét làm mịn các yêu cầu thành hàng đợi và sau đó trả lời không đồng bộ. –

+3

Tôi không đồng ý: một số lỗi trong máy chủ SQL vốn đã có thể thử lại. Nếu bạn nhấn máy chủ trong khi xây dựng lại chỉ mục ví dụ, việc chờ và thử lại là vô hại và tránh hỗ trợ không cần thiết hoặc ghi nhật ký – gbn

+0

Tìm ra lý do tại sao truy vấn thất bại là ý tưởng hay nhưng không đặc biệt phòng thủ. Như tôi đã chỉ ra trong câu hỏi ban đầu của tôi, chức năng đang thực hiện tìm kiếm văn bản miễn phí và ngoại lệ phản ánh điều đó. Tôi đã sử dụng Trình quản lý cấu hình máy chủ Sql để tự động khởi động dịch vụ bộ lọc FullText nhưng tôi vẫn có thể nhận được ngoại lệ về yêu cầu đầu tiên. Đây là trên máy phát triển của tôi bằng cách sử dụng một cài đặt cục bộ của Sql Server. –

-6

Kéo mã có liên quan ra theo phương pháp riêng của nó, sau đó sử dụng đệ quy.

Pseudo-code:

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(); 
} 
+0

Và nếu máy chủ đang ngoại tuyến trong 2 giờ bạn đề xuất tiếp tục thử ...? – gbn

+0

@ gbn- Không, tất nhiên là không. Chúng tôi chỉ cho anh ta khái niệm ở đây, không viết toàn bộ mã của anh ấy. Xem chỉnh sửa của tôi. – MAW74656

+0

Có thể một số sự kết hợp giữa bộ đếm của anh ta và sự trừu tượng của tôi sẽ là tốt nhất. – MAW74656

8

enum của tôi về retryables cho sql trông như thế này :

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

Bạn có thể chỉ cần sử dụng thuộc tính SqlConnectionStringBuilder để thử lại kết nối sql.

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

Lưu ý: - Bắt buộc .Net 4.5 hoặc mới hơn.

Các vấn đề liên quan