Domanda

Ho una classe "Database" che funziona come wrapper per ADO.net. Ad esempio, quando devo eseguire una procedura, chiamo Database.ExecuteProcedure (procedureName, parametriAndItsValues).

Stiamo riscontrando seri problemi con le situazioni di deadlock in SQL Server 2000. Parte del nostro team sta lavorando sul codice sql e sulle transazioni per ridurre al minimo questi eventi, ma sto pensando di rendere robusta questa classe di database contro situazioni di deadlock.

Vogliamo che la vittima del deadlock riprovi forse dopo un po 'di tempo, ma non so se sia possibile. Ecco il codice per un metodo che usiamo:

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

Posso eseguire questa gestione all'interno di un blocco catch?

È stato utile?

Soluzione

In primo luogo, vorrei rivedere il mio codice SQL 2000 e arrivare alla fine del perché questo deadlock si sta verificando. Risolvere questo problema potrebbe nascondere un problema più grande (ad es. Indice mancante o query errata).

Secondo, rivederei la mia architettura per confermare che l'istruzione deadlock debba davvero essere chiamata in modo che frequentemente ( select count (*) from bob deve essere chiamato 100 volte al secondo?).

Tuttavia, se hai davvero bisogno di un po 'di supporto per i deadlock e non hai errori nel tuo SQL o nella tua architettura, prova qualcosa seguendo le seguenti linee. (Nota: ho dovuto usare questa tecnica per un sistema che supporta migliaia di query al secondo e avrebbe colpito i deadlock abbastanza raramente)

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

Altri suggerimenti

Basandomi sulla risposta di @ Sam, presento un metodo wrapper per tentativi di uso generale:

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

Se il deadlock può essere risolto a livello di dati, questa è sicuramente la strada da percorrere. Suggerimenti di blocco, riprogettazione del modo in cui il modulo funziona e così via. NoLock non è una panacea però - a volte non è possibile utilizzarlo per motivi di integrità transazionale e ho avuto casi di letture di dati semplici (anche se complessi) con tutte le tabelle pertinenti NoLock'd che causavano ancora blocchi su altre query.

Comunque - se non riesci a risolverlo a livello di dati per qualsiasi motivo, che ne dici di

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

Se riscontri problemi con i deadlock, sarebbe meglio vedere cosa sta facendo il codice SQL. Ad esempio, i deadlock di escalation dei blocchi sono molto facili da creare se si dispone di un livello di isolamento serializzabile (o qualunque sia l'equivalente nei rdbms) - e può essere mitigato in alcuni modi, come riordinare le query o (in SQL Server almeno) usando (UPDLOCK) per prendere un blocco di scrittura prima (quindi non si ottiene un blocco di lettura concorrente).

Il nuovo tentativo verrà mischiato ... ad esempio, se si è in un TransactionScope, potrebbe essersi già interrotto. Ma solo a livello purista - se ho problemi a parlare con il db, voglio che il mio codice diventi un panico e un panico precoce ... riprovare sembra un po 'confuso in questo particolare scenario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top