Was sind die Hauptursachen für Deadlocks und können sie verhindert werden?

55

Kürzlich hat eine unserer ASP.NET-Anwendungen einen Datenbank-Deadlock-Fehler angezeigt und ich wurde aufgefordert, den Fehler zu überprüfen und zu beheben. Es gelang mir herauszufinden, dass die Ursache für den Deadlock eine gespeicherte Prozedur war, die eine Tabelle innerhalb eines Cursors rigoros aktualisierte.

Dies ist das erste Mal, dass ich diesen Fehler sehe und nicht weiß, wie ich ihn effektiv verfolgen und beheben kann. Ich habe alle mir bekannten Möglichkeiten ausprobiert und festgestellt, dass die zu aktualisierende Tabelle keinen Primärschlüssel hat! Zum Glück war es eine Identitätssäule.

Ich fand später den Entwickler, der die Datenbank für die Bereitstellung skriptete, durcheinander. Ich habe einen Primärschlüssel hinzugefügt und das Problem wurde behoben.

Ich fühlte mich glücklich und kehrte zu meinem Projekt zurück und recherchierte, um den Grund für diesen Stillstand herauszufinden ...

Anscheinend war es eine Wartebedingung, die den Deadlock verursachte. Aktualisierungen dauern ohne Primärschlüssel anscheinend länger als mit Primärschlüssel.

Ich weiß, dass es keine genau definierte Schlussfolgerung ist, deshalb poste ich hier ...

  • Ist der fehlende Primärschlüssel das Problem?
  • Gibt es andere Bedingungen, die einen Deadlock verursachen als (gegenseitiger Ausschluss, Halten und Warten, keine Vorwegnahme und zirkuläres Warten)?
  • Wie verhindere und verfolge ich Deadlocks?
CoderHawk
quelle
2
IME-Mehrheit (alle?) Der Deadlocks, die ich gesehen habe, ist auf zirkuläre Wartezeiten zurückzuführen (hauptsächlich auf die übereifrige Verwendung von Triggern).
Sathyajith Bhat
Zirkularität ist eine der notwendigen Bedingungen eines Deadlocks. Sie können jeden Deadlock vermeiden, wenn alle Ihre Sitzungen Sperren in derselben Reihenfolge erhalten.
Peter G.

Antworten:

38

Deadlocks zu verfolgen ist das einfachere von beiden:

Standardmäßig werden Deadlocks nicht in das Fehlerprotokoll geschrieben. Sie können SQL veranlassen, Deadlocks mit den Ablaufverfolgungsflags 1204 und 3605 in das Fehlerprotokoll zu schreiben.

Schreiben Sie Deadlock-Informationen in das SQL Server-Fehlerprotokoll: DBCC TRACEON (-1, 1204, 3605)

Ausschalten: DBCC TRACEOFF (-1, 1204, 3605)

Unter "Fehlerbehebung bei Deadlocks" finden Sie eine Erläuterung des Ablaufverfolgungsflags 1204 und der Ausgabe, die Sie erhalten, wenn es eingeschaltet wird. https://msdn.microsoft.com/en-us/library/ms178104.aspx

Prävention ist schwieriger, im Wesentlichen muss man auf Folgendes achten:

Der Codeblock 1 sperrt zuerst die Ressource A und dann die Ressource B in dieser Reihenfolge.

Der Codeblock 2 sperrt zuerst die Ressource B und dann die Ressource A in dieser Reihenfolge.

Dies ist der klassische Zustand, in dem ein Deadlock auftreten kann. Wenn das Sperren beider Ressourcen nicht atomar ist, der Codeblock 1 A sperren und vorentleert werden kann, sperrt Codeblock 2 B, bevor A die Verarbeitungszeit zurückerhält. Jetzt haben Sie festgefahren.

Um diesen Zustand zu verhindern, können Sie folgendermaßen vorgehen

Codeblock A (Pseudocode)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Codeblock B (Pseudocode)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

Vergessen Sie nicht, A und B zu entsperren, wenn Sie damit fertig sind

Dies würde die Blockierung zwischen Codeblock A und Codeblock B verhindern

Aus Datenbanksicht bin ich mir nicht sicher, wie ich dies verhindern soll, da Sperren von der Datenbank selbst behandelt werden, dh Zeilen- / Tabellensperren beim Aktualisieren von Daten. Wo ich gesehen habe, dass die meisten Probleme auftreten, ist, wo Sie Ihre gesehen haben, innerhalb eines Cursors. Cursor sind notorisch ineffizient. Vermeiden Sie sie, wenn dies möglich ist.

BlackICE
quelle
Wollten Sie Ressource A vor Ressource B in Codeblock B sperren? Wie geschrieben, führt dies zu Deadlocks. Wie Sie selbst in den Kommentaren zuvor erwähnt haben. So weit wie möglich möchten Sie Ressourcen immer in derselben Reihenfolge sperren, auch wenn Sie zu Beginn Dummy-Abfragen benötigen, um diese Sperrreihenfolge sicherzustellen.
Gerard ONeill
23

Meine Lieblingsartikel zum Lesen und Lernen von Deadlocks sind: Simple Talk - Aufspüren von Deadlocks und SQL Server Central - Verwenden von Profiler zum Auflösen von Deadlocks . Sie werden Ihnen Beispiele und Ratschläge geben, wie Sie mit einer Situation umgehen können.

Kurz gesagt, um ein aktuelles Problem zu lösen, würde ich die beteiligten Transaktionen kürzer machen, den nicht benötigten Teil aus ihnen herausnehmen, die Reihenfolge der Verwendung der Objekte regeln, sehen, welche Isolationsstufe tatsächlich benötigt wird, und nicht unnötig lesen Daten...

Aber lesen Sie besser die Artikel, sie werden in Ratschlägen viel netter sein.

Marian
quelle
16

Manchmal kann ein Deadlock durch Hinzufügen einer Indizierung behoben werden, da die Datenbank einzelne Datensätze und nicht die gesamte Tabelle sperren kann, wodurch Konflikte und die Gefahr von Datenstaus verringert werden.

Zum Beispiel in InnoDB :

Wenn Sie keine für Ihre Anweisung geeigneten Indizes haben und MySQL die gesamte Tabelle durchsuchen muss, um die Anweisung zu verarbeiten, wird jede Zeile der Tabelle gesperrt, wodurch wiederum alle Einfügungen anderer Benutzer in die Tabelle blockiert werden. Es ist wichtig, gute Indizes zu erstellen, damit Ihre Abfragen nicht unnötig viele Zeilen durchsuchen.

Eine andere gängige Lösung besteht darin, die Transaktionskonsistenz zu deaktivieren, wenn sie nicht benötigt wird, oder auf andere Weise die Isolationsstufe zu ändern , z. B. ein langwieriger Job zum Berechnen von Statistiken. wie sie sich unter dir verändern. Wenn der Vorgang 30 Minuten dauert, möchten Sie nicht, dass alle anderen Transaktionen in diesen Tabellen angehalten werden.

...

Die Nachverfolgung hängt von der von Ihnen verwendeten Datenbanksoftware ab.

Joe
quelle
Es ist übliche Höflichkeit, beim Downvoting Kommentare zu hinterlassen ... Dies ist eine gültige Antwort. Eine select-Anweisung, die auf eine Tabellensperre aktualisiert und für immer in Anspruch genommen wird, kann mit Sicherheit einen Deadlock verursachen.
BlackICE
1
MS SQLServer kann auch zu unerwartetem Sperrverhalten führen, wenn die Indizes nicht geclustert sind. Es ignoriert stillschweigend Ihre Anweisung, das Sperren auf Zeilenebene zu verwenden, und führt das Sperren auf Seitenebene durch. Sie können dann Deadlocks auf der Seite warten lassen.
Jay
7

Nur um auf dem Cursor etwas zu entwickeln. es ist in der Tat wirklich schlimm. Es sperrt die gesamte Tabelle und verarbeitet die Zeilen nacheinander.

Es ist am besten, Zeilen in der Art eines Cursors mit einer while-Schleife zu durchlaufen

In der while-Schleife wird für jede Zeile in der Schleife eine Auswahl durchgeführt, und die Sperre wird jeweils nur für eine Zeile ausgeführt. Die restlichen Daten in der Tabelle können frei abgefragt werden, wodurch die Wahrscheinlichkeit eines Deadlocks verringert wird.

Außerdem ist es schneller. Sie wundern sich, warum es überhaupt Cursor gibt.

Hier ist ein Beispiel für diese Art von Struktur:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Wenn Ihr ID-Feld spärlich ist, möchten Sie möglicherweise eine separate Liste von IDs abrufen und diese durchlaufen:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END
Nicolas de Fontenay
quelle
6

Das Fehlen eines Primärschlüssels ist nicht das Problem. Zumindest für sich. Erstens benötigen Sie keine Primärdaten, um über Indizes zu verfügen. Zweitens führt eine Tabellensperre nicht automatisch zu einem Deadlock, selbst wenn Sie Tabellenscans durchführen (was passieren muss, wenn Ihre bestimmte Abfrage keinen Index verwendet). Ein Schreibvorgang würde auf einen Lesevorgang warten, und ein Lesevorgang würde es tun warte auf ein schreiben, und natürlich müssten die leser überhaupt nicht aufeinander warten.

Zusätzlich zu den anderen Antworten ist die Transaktionsisolationsstufe von Bedeutung, da wiederholbares Lesen und Serialisieren dazu führen, dass Lesesperren bis zum Ende der Transaktion aufrechterhalten werden. Das Sperren einer Ressource führt nicht zu einem Deadlock. Es reicht aus, es verschlossen zu halten. Schreibvorgänge lassen ihre Ressource immer bis zum Ende der Transaktion gesperrt.

Meine bevorzugte Strategie zur Verhinderung von Sperren ist die Verwendung der "Schnappschuss" -Funktionen. Die Read Committed Snapshot-Funktion bedeutet, dass beim Lesen keine Sperren verwendet werden! Und wenn Sie mehr Kontrolle als "Read commit" benötigen, gibt es die Funktion "Snap shot isolation level". Dies ermöglicht eine serialisierte Transaktion (hier unter Verwendung von MS-Begriffen), ohne die anderen Spieler zu blockieren.

Schließlich kann eine Klasse von Deadlocks durch die Verwendung einer Aktualisierungssperre verhindert werden. Wenn Sie den Lesevorgang lesen und halten (HOLD oder Repeatable Read) und ein anderer Vorgang dasselbe ausführt, versuchen beide, dieselben Datensätze zu aktualisieren, ist ein Deadlock aufgetreten. Wenn jedoch beide eine Aktualisierungssperre anfordern, wartet der zweite Prozess auf den ersten, während andere Prozesse die Daten mit gemeinsamen Sperren lesen können, bis die Daten tatsächlich geschrieben wurden. Dies funktioniert natürlich nicht, wenn einer der Prozesse weiterhin eine gemeinsam genutzte HOLD-Sperre anfordert.

Gerard ONeill
quelle
-2

Während Cursor in SQL Server langsam sind, können Sie ein Deadlock in einem Cursor vermeiden, indem Sie die Quelldaten für den Cursor in eine Temp-Tabelle ziehen und den Cursor darauf ausführen. Dadurch wird verhindert, dass der Cursor die eigentliche Datentabelle sperrt. Die einzigen Sperren, die Sie erhalten, betreffen Aktualisierungen oder Einfügungen im Cursor, die nur für die Dauer der Einfügung / Aktualisierung und nicht für die Dauer des Cursors gehalten werden.

Bill Joyce
quelle