Как поймать SqlException, вызванное взаимоблокировкой?

из приложения .NET 3.5 / c#, я хотел бы поймать SqlException но только если это вызвано тупиками на экземпляре SQL Server 2008.

типичное сообщение об ошибке Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

тем не менее, это не похоже на документально код ошибки для этого исключения.

исключение фильтрации против присутствия тупик ключевое слово в их сообщении кажется очень уродливым способом добиться такого поведения. Кто-нибудь знает правильный способ сделать это?

3 ответов


код ошибки Microsft SQL Server для взаимоблокировки-1205, поэтому вам нужно будет обработать SqlException и проверить это. Итак, например, если для всех других типов SqlException вы хотите, чтобы пузырь исключение:

catch (SqlException ex)
{
    if (ex.Number == 1205)
    {
        // Deadlock 
    }
    else
        throw;
}

или, используя фильтрацию исключений, доступную в C# 6

catch (SqlException ex) when (ex.Number == 1205)
{
    // Deadlock 
}

удобная вещь, чтобы найти фактический код ошибки SQL для данного сообщения, - это посмотреть в sys.сообщения в SQL Server.

например

SELECT * FROM sys.messages WHERE text LIKE '%deadlock%' AND language_id=1033

An альтернативный способ обработки блокировок (начиная с SQL Server 2005 и выше) - это сделать это в хранимой процедуре с помощью TRY...Поддержка CATCH:

BEGIN TRY
    -- some sql statements
END TRY
BEGIN CATCH
    IF (ERROR_NUMBER() = 1205)
        -- is a deadlock
    ELSE
        -- is not a deadlock
END CATCH

есть полный пример здесь в MSDN о том, как реализовать логику повтора взаимоблокировки чисто в SQL.


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

взаимоблокировка, обнаруженная базой данных, будет эффективно откатывать транзакцию, в которой вы работали (если таковые имеются), в то время как соединение остается открытым .Сеть. Повторная попытка этой операции (в той же связи) означает, что она будет выполняться в контексте без транзакций, и это может привести к повреждению данных.

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

поэтому, когда вы повторяете неудачную операцию, убедитесь, что вы открываете совершенно новое соединение и начинаете новую транзакцию.


вот способ обнаружения тупиков C# 6.

try
{
    //todo: Execute SQL. 
    //IMPORTANT, if you used Connection.BeginTransaction(), this try..catch must surround that code. You must rollback the original transaction, then recreate it and re-run all the code.
}
catch (SqlException ex) when (ex.Number == 1205)
{
    //todo: Retry SQL
}

убедитесь, что это попробовать..catch окружает всю вашу транзакцию. Согласно @Steven (см. Его ответ для деталей), когда команда sql терпит неудачу из-за взаимоблокировки, она вызывает откат транзакции, и если вы не воссоздадите транзакцию, ваша повторная попытка будет выполняться вне контекста транзакции и может привести к несоответствиям данных.