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

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

Из приложения .NET 3.5/С# я хотел бы поймать 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.

Однако для этого исключения он не является документированным кодом ошибки.

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

4b9b3361

Ответ 1

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

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

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

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

Чтобы найти фактический код ошибки SQL для данного сообщения, удобно заглянуть в файл sys.messages в SQL Server.

например,

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

Альтернативный способ обработки взаимоблокировок (от 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.

Ответ 2

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

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

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

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

Ответ 3

Вот способ обнаружения тупиковых ситуаций в С# 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
}

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