Wie kann man eine durch Deadlock verursachte SqlException abfangen?

92

Von einer .NET 3.5 / C # -App möchte ich abfangen, SqlExceptionaber nur, wenn dies durch Deadlocks auf einer SQL Server 2008-Instanz verursacht wird.

Typische Fehlermeldung ist Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Es scheint jedoch kein dokumentierter Fehlercode für diese Ausnahme zu sein.

Das Filtern einer Ausnahme gegen das Vorhandensein des Deadlock- Schlüsselworts in der Nachricht scheint ein sehr hässlicher Weg zu sein, um dieses Verhalten zu erreichen. Kennt jemand den richtigen Weg dazu?

Joannes Vermorel
quelle
3
Ich habe (endlich) die Dokumentation für den Fehlercode gefunden: msdn.microsoft.com/en-us/library/aa337376.aspx . Sie können dies auch über SQL Server selbst finden:select * from master.dbo.sysmessages where error=1205
Martin McNulty

Antworten:

153

Der Microsft SQL Server-spezifische Fehlercode für einen Deadlock lautet 1205. Sie müssen also die SqlException behandeln und dies überprüfen. Also, zB wenn für alle anderen Arten von SqlException die Blase die Ausnahme sein soll:

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

Oder verwenden Sie die in C # 6 verfügbare Ausnahmefilterung

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

Um den tatsächlichen SQL-Fehlercode für eine bestimmte Nachricht zu finden, sollten Sie in sys.messages in SQL Server nachsehen.

z.B

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

Eine alternative Methode zum Behandeln von Deadlocks (ab SQL Server 2005) besteht darin, dies innerhalb einer gespeicherten Prozedur mithilfe der TRY ... CATCH-Unterstützung zu tun:

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

Es gibt ein vollständiges Beispiel hier in MSDN, wie Deadlock Wiederholungslogik rein in SQL zu implementieren.

AdaTheDev
quelle
2
Beachten Sie, dass die Fehlercodes herstellerspezifisch sind, sodass 1205 ein Deadlock für SQL Server ist, für Oracle, MySQL usw. jedoch möglicherweise anders.
brianmearns
3
Abhängig von der Datenschicht SqlExceptionkann die in eine andere eingeschlossen werden. Daher müssen wir möglicherweise jede Art von Ausnahme abfangen und sie überprüfen. Wenn es sich nicht direkt um eine Deadlock-Ausnahme handelt, müssen Sie sie rekursiv überprüfen InnerException.
Frédéric
46

Da Sie möglicherweise Deadlocks erkennen möchten, um den fehlgeschlagenen Vorgang wiederholen zu können, warne ich Sie gerne vor einem kleinen Problem. Ich hoffe, Sie entschuldigen mich dafür, dass ich hier ein bisschen vom Thema abkomme.

Ein von der Datenbank erkannter Deadlock führt ein Rollback der Transaktion durch, in der Sie ausgeführt wurden (falls vorhanden), während die Verbindung in .NET geöffnet bleibt. Wenn Sie diesen Vorgang (in derselben Verbindung) wiederholen, wird er in einem transaktionslosen Kontext ausgeführt, was zu einer Beschädigung der Daten führen kann.

Es ist wichtig, sich dessen bewusst zu sein. Es ist am besten, die vollständige Verbindung im Falle eines durch SQL verursachten Fehlers als zum Scheitern verurteilt zu betrachten. Das Wiederholen des Vorgangs kann nur auf der Ebene durchgeführt werden, auf der die Transaktion definiert ist (durch Neuerstellen dieser Transaktion und ihrer Verbindung).

Wenn Sie also einen fehlgeschlagenen Vorgang wiederholen, stellen Sie sicher, dass Sie eine vollständig neue Verbindung öffnen und eine neue Transaktion starten.

Steven
quelle
4
Warum brauchen Sie eine völlig neue Verbindung? Ich habe eine Frage zu dieser Antwort gepostet hier .
Sam
3

Hier ist eine C # 6-Methode zum Erkennen von Deadlocks.

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
}

Stellen Sie sicher, dass dieser try..catch Ihre gesamte Transaktion umgibt. Laut @Steven (Einzelheiten finden Sie in seiner Antwort) wird die Transaktion zurückgesetzt, wenn der Befehl sql aufgrund des Deadlocks fehlschlägt. Wenn Sie die Transaktion nicht neu erstellen, wird Ihr Wiederholungsversuch außerhalb des Kontexts von ausgeführt die Transaktion und kann zu Dateninkonsistenzen führen.

Brian
quelle