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

94

Из приложения .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.

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

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

Джоаннес Верморель
источник
3
Я (наконец) нашел документацию по коду ошибки: msdn.microsoft.com/en-us/library/aa337376.aspx . Вы также можете найти это через сам SQL Server:select * from master.dbo.sysmessages where error=1205
Мартин МакНалти,

Ответы:

157

Код ошибки Microsoft 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.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.

AdaTheDev
источник
2
Обратите внимание, что коды ошибок зависят от производителя, поэтому 1205 - это тупик для SQL Server, но он может быть другим для Oracle, MySQL и т. Д.
brianmearns
3
В зависимости от уровня данных он SqlExceptionможет быть заключен в другой. Таким образом, нам может потребоваться перехватить любые исключения и проверить их, а затем, если они не являются непосредственно исключением тупиковой ситуации, рекурсивно проверить их InnerException.
Frédéric
46

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

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

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

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

Стивен
источник
4
Зачем нужно совершенно новое соединение? Я отправил вопрос об этом ответе здесь .
Сэм
3

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

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

Брайан
источник