Как получить эффективную обработку взаимоблокировок Sql Server в C # с ADO? - PullRequest
19 голосов
/ 26 ноября 2008

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

У нас серьезные проблемы с ситуациями тупиковой ситуации в 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?

Ответы [ 4 ]

34 голосов
/ 03 декабря 2008

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

Во-вторых, я бы пересмотрел свою архитектуру, чтобы убедиться, что действительно нужно часто вызывать оператор взаимоблокировки (должен ли 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;
  }
}
21 голосов
/ 14 июля 2011

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

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;
        }
    });
}
5 голосов
/ 02 декабря 2008

Если тупик можно устранить на уровне данных, это определенно верный путь. Блокировка подсказок, изменение дизайна модуля и так далее. Но 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;
    }
}
3 голосов
/ 26 ноября 2008

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

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

...