Jak przechwycić wyjątek SqlException spowodowany zakleszczeniem?

94

Z aplikacji .NET 3.5 / C # chciałbym wychwycić, SqlExceptionale tylko wtedy, gdy jest to spowodowane zakleszczeniami w wystąpieniu SQL Server 2008.

Typowy komunikat o błędzie to Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Jednak nie wydaje się, aby był to udokumentowany kod błędu dla tego wyjątku.

Filtrowanie wyjątku od obecności słowa kluczowego zakleszczenia w ich wiadomości wydaje się być bardzo brzydkim sposobem osiągnięcia tego zachowania. Czy ktoś wie, jak to zrobić?

Joannes Vermorel
źródło
3
W końcu znalazłem dokumentację dotyczącą kodu błędu: msdn.microsoft.com/en-us/library/aa337376.aspx . Możesz to również znaleźć za pośrednictwem samego SQL Server:select * from master.dbo.sysmessages where error=1205
Martin McNulty,

Odpowiedzi:

157

Kod błędu specyficzny dla Microsft SQL Server dla zakleszczenia to 1205, więc musisz obsłużyć SqlException i sprawdzić to. Na przykład, jeśli dla wszystkich innych typów SqlException chcesz, aby bąbelek wyjątek był górny:

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

Lub używając filtrowania wyjątków dostępnego w C # 6

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

Przydatną rzeczą do znalezienia rzeczywistego kodu błędu SQL dla danej wiadomości jest zajrzenie do sys.messages w SQL Server.

na przykład

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

Alternatywnym sposobem obsługi zakleszczeń (z SQL Server 2005 i nowszych) jest zrobienie tego w ramach procedury składowanej przy użyciu obsługi 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

Jest to pełny przykład tutaj w MSDN sposobu realizacji zakleszczenia logikę ponawiania wyłącznie wewnątrz SQL.

AdaTheDev
źródło
2
Zwróć uwagę, że kody błędów są specyficzne dla dostawcy, więc 1205 to impas dla SQL Server, ale może być inny dla Oracle, MySQL itp.
brianmearns
3
W zależności od warstwy danych SqlExceptionmoże być opakowana w inną. Może więc być konieczne złapanie dowolnego rodzaju wyjątku i sprawdzenie go, a następnie, jeśli nie są bezpośrednio wyjątkiem zakleszczenia, rekurencyjnie sprawdź ich InnerException.
Frédéric
46

Ponieważ przypuszczam, że prawdopodobnie chcesz wykryć zakleszczenia, aby móc ponowić nieudaną operację, chciałbym Cię ostrzec, żebyś się trochę zorientował. Mam nadzieję, że wybaczycie mi, że jestem tu trochę poza tematem.

Zakleszczenie wykryte przez bazę danych skutecznie wycofa transakcję, w której byłeś uruchomiony (jeśli wystąpiła), podczas gdy połączenie jest otwarte w .NET. Ponawianie tej operacji (w tym samym połączeniu) oznacza, że ​​zostanie ona wykonana w kontekście bez transakcji, co może prowadzić do uszkodzenia danych.

Należy być tego świadomym. Najlepiej jest uznać, że całe połączenie jest skazane na porażkę w przypadku awarii spowodowanej przez SQL. Ponawianie operacji można wykonać tylko na poziomie, na którym transakcja jest zdefiniowana (poprzez odtworzenie tej transakcji i jej połączenia).

Więc kiedy ponawiasz nieudaną operację, upewnij się, że otworzyłeś zupełnie nowe połączenie i rozpocznij nową transakcję.

Steven
źródło
4
Dlaczego potrzebujesz zupełnie nowego połączenia? I zostały zaksięgowane pytanie o odpowiedź tutaj .
Sam
3

Oto sposób wykrywania zakleszczeń w języku 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
}

Upewnij się, że ta try..catch otacza całą transakcję. Według @Stevena (zobacz jego odpowiedź po szczegóły), gdy polecenie sql nie powiedzie się z powodu zakleszczenia, powoduje to wycofanie transakcji, a jeśli nie utworzysz ponownie transakcji, ponowna próba zostanie wykonana poza kontekstem transakcji i może skutkować niespójnością danych.

Brian
źródło