Подтвердить что ты не робот

Как получить эффективную обработку блокировки сервера Sql в С# с помощью ADO?

У меня есть класс "База данных", который работает как оболочка для ADO.net. Например, когда мне нужно выполнить процедуру, я вызываю Database.ExecuteProcedure(procedureName, parametersAndItsValues).

Мы столкнулись с серьезными проблемами с ситуациями Deadlock в SQL Server 2000. Часть нашей команды работает над кодом и транзакциями sql для минимизации этих событий, но я думаю о том, чтобы сделать этот класс базы данных надежным в ситуациях взаимоблокировки.

Мы хотим, чтобы жертва тупика пыталась повторить попытку, возможно, через некоторое время, но я не знаю, возможно ли это. Вот код для метода, который мы используем:

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

Могу ли я сделать эту обработку внутри блока catch?

4b9b3361

Ответ 1

Во-первых, я бы пересмотрел свой код SQL 2000 и понял, почему этот тупик происходит. Исправление этого может скрыть большую проблему (например, отсутствующий индекс или плохой запрос).

Во-вторых, я бы пересмотрел свою архитектуру, чтобы подтвердить, что оператор deadlocking действительно нужно часто называть (следует ли select count(*) from bob вызывать 100 раз в секунду?).

Однако, если вам действительно нужна поддержка тупика и нет ошибок в вашем SQL или архитектуре, попробуйте что-нибудь в следующих строках. (Примечание: мне пришлось использовать этот метод для системы, поддерживающей тысячи запросов в секунду, и очень редко ударял бы в тупик)

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

Ответ 2

Основываясь на ответе @Sam, я представляю метод обертки общего назначения:

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

Ответ 3

Если тупик может быть разрешен на уровне данных, это определенно способ пойти. Блокировка подсказок, пересмотр способа работы модуля и т.д. NoLock - это не панацея, хотя иногда это невозможно использовать по соображениям целостности транзакций, и у меня были случаи прямого (хотя и сложного) чтения данных со всеми соответствующими таблицами NoLock'd, которые по-прежнему вызывали блоки по другим запросам.

В любом случае - если вы не можете решить его на уровне данных по любой причине, как насчет

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

Ответ 4

Если у вас возникают проблемы с взаимоблокировками, было бы лучше посмотреть, что делает код SQL. Например, блокировка блокировки блокировки очень проста в создании, если у вас есть сериализуемый уровень изоляции (или что-то подобное в ваших rdbms) - и может быть смягчено несколькими способами, такими как повторный заказ запросов или (в SQL Server по крайней мере) с помощью (UPDLOCK) для блокировки записи раньше (так что вы не получаете конкурирующего чтения-блокировки).

Повторная попытка будет смешанной... например, если вы находитесь в TransactionScope, она, возможно, уже прервана. Но как раз на уровне пуриста - если у меня возникают проблемы с разговором с db, я хочу, чтобы мой код паниковал, и панику рано... Повторите попытку, кажется, немного взломанный в этом конкретном сценарии.