Question

J'ai une classe "Database" qui fonctionne comme un wrapper pour ADO.net. Par exemple, lorsque j'ai besoin d'exécuter une procédure, j'appelle Database.ExecuteProcedure (procedureName, parametersAndItsValues).

Nous rencontrons de graves problèmes liés aux situations d'interblocage dans SQL Server 2000. Une partie de notre équipe travaille sur le code SQL et les transactions afin de minimiser ces événements, mais je songe à rendre cette classe Database résistante aux situations d'interblocage.

Nous souhaitons que la victime de l'impasse réessaye peut-être après un certain délai, 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 catch?

Était-ce utile?

La solution

Tout d’abord, j’examinerais mon code SQL 2000 et comprendrait pourquoi cette impasse se produisait. Corriger ceci peut cacher un problème plus important (par exemple, index manquant ou mauvaise requête).

Deuxièmement, j'examinerais mon architecture pour confirmer que la déclaration d'interblocage doit vraiment être appelée fréquemment (le select count (*) de bob doit-il être appelé 100 fois par seconde?).

Toutefois, si vous avez vraiment besoin d’une prise en charge des interblocages et que vous n’avez aucune erreur dans votre SQL ou votre architecture, essayez quelque chose dans les lignes suivantes. (Remarque: j'ai dû utiliser cette technique pour un système prenant en charge des milliers de requêtes par seconde et j'arrivais très rarement à des blocages.)

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

Autres conseils

En m'appuyant sur la réponse de @ Sam, je présente une méthode d'encapsulation de nouvelle tentative à 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;
        }
    });
}

Si le blocage peut être résolu au niveau de la couche de données, c'est vraiment la voie à suivre. Verrouillage des astuces, refonte du fonctionnement du module, etc. Cependant, NoLock n’est pas une panacée - il est parfois impossible de l’utiliser pour des raisons d’intégrité transactionnelle et j’ai eu des cas de lectures de données simples (bien que complexes) avec toutes les tables pertinentes NoLock'd qui provoquaient toujours le blocage de d’autres requêtes.

Quoi qu'il en soit - si vous ne pouvez pas le résoudre au niveau de la couche de données pour une raison quelconque, pourquoi pas

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

Si vous rencontrez des problèmes d'interblocages, il serait préférable de regarder ce que fait le code SQL. Par exemple, les blocages d’escalade de verrous sont très faciles à créer si vous avez un niveau d’isolation sérialisable (ou l’équivalent dans votre rdbms). Ils peuvent être atténués de plusieurs manières, telles que la réorganisation des requêtes au moins) en utilisant (UPDLOCK) pour prendre un verrou en écriture plus tôt (pour ne pas obtenir un verrou en lecture concurrent).

Réessayer va être mélangé ... par exemple, si vous êtes dans un TransactionScope, il se peut que celui-ci soit déjà abandonné. Mais juste au niveau puriste - si j’ai des problèmes pour parler à la base de données, je veux que mon code panique, et panique plus tôt ... réessayer semble un peu maladroit dans ce scénario particulier.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top