2008-11-26 10 views
19

J'ai une classe 'Database' qui fonctionne comme un wrapper pour ADO.net. Par exemple, quand j'ai besoin d'exécuter une procédure, j'appelle Database.ExecuteProcedure (procedureName, parametersAndItsValues). Nous rencontrons de graves problèmes avec les situations de blocage dans SQL Server 2000. Une partie de notre équipe travaille sur le code sql et les transactions pour minimiser ces événements, mais je pense à rendre cette classe de base de données robuste contre les situations de blocage.Comment obtenir une gestion efficace des interblocages Sql Server en C# avec ADO?

Nous voulons que la victime de l'impasse réessaye peut-être après un certain temps, mais je ne sais pas si c'est possible. Voici le code d'une méthode que nous utilisons:

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

Puis-je faire cette manipulation à l'intérieur d'un bloc d'arrêt?

Répondre

31

D'abord, je voudrais revoir mon SQL code 2000 et aller au fond des raisons pour lesquelles cette impasse se passe . Corriger cela peut cacher un plus gros problème (par exemple, un index manquant ou une mauvaise requête).

Deuxièmement, je passerais en revue mon architecture pour confirmer que l'interblocage doit être appelé fréquemment (select count(*) from bob doit-il être appelé 100 fois par seconde?). Cependant, si vous avez vraiment besoin d'une prise en charge de l'interblocage et n'avez pas d'erreurs dans votre SQL ou votre architecture, essayez quelque chose le long des lignes suivantes. (Note: J'ai eu à utiliser cette technique pour un système supportant des milliers de requêtes par seconde et frapperait les interblocages assez rarement)

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

Envisager d'ajouter un court délai dans la boucle de réessai comme 'Thread.Sleep (100);' après 'retryCount -;' pour éviter de frapper SQL Server si fort. –

2

Si vous rencontrez des problèmes avec des blocages, il serait préférable de regarder ce que fait le code SQL. Par exemple, les interblocages lock-escalation sont très faciles à créer si vous avez un niveau d'isolation sérialisable (ou quel que soit l'équivalent dans votre rdbms) - et peuvent être atténués de plusieurs façons, telles que le réordonnancement des requêtes, ou (dans SQL Server au moins) en utilisant le (UPDLOCK) pour prendre un verrou en écriture plus tôt (de sorte que vous n'obtenez pas un verrou de lecture concurrent). La nouvelle tentative va être mélangée ... par exemple, si vous êtes dans un TransactionScope, il se peut qu'il ait déjà été abandonné. Mais juste au niveau puriste - si j'ai des problèmes à parler à la base de données, je veux que mon code panique, et panique tôt ... re-essayer semble un peu hacky dans ce scénario particulier.

+0

Nous considérons tout le code sql et les transactions, ceci étant fait, nous n'essayons pas seulement de tricher avec un blocage en rappelant C#. Mais la concurrence peut créer des victimes de blocage, et pour cette raison, nous voulons rendre nos applications plus robustes quand cela arrive. –

+0

Victor - vous pouvez et devez faire face aux blocages en les résolvant sur la couche de données. Une fois résolu, vous n'avez rien à faire en C#. –

+5

@Dave - c'est sans doute trop simpliste –

5

Si l'interblocage peut être résolu sur la couche de données, c'est définitivement la solution. Verrouillage des indices, redéfinition du fonctionnement du module, etc. NoLock n'est pas une panacée - parfois il n'est pas possible d'utiliser pour des raisons d'intégrité transactionnelle et j'ai eu des cas de lectures de données droites (quoique complexes) avec toutes les tables pertinentes NoLock'd qui causaient encore des blocages sur d'autres requêtes.

Quoi qu'il en soit - si vous ne pouvez pas résoudre à la couche de données pour une raison quelconque, que diriez-vous

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

Je suis intéressé à utiliser la solution ci-dessus, mais j'apprécierais plus d'informations sur comment exactement cela pourrait fonctionner pour mon cas. – Kobojunkie

+2

upvote pour le temporisateur aléatoire, la solution Sams martèle le serveur SQL – DeveloperChris

19

bâtiment sur @ réponse de Sam, je présente une méthode d'emballage de nouvelle tentative d'usage général:

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