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?
quelle
Antworten:
Deadlocks zu verfolgen ist das einfachere von beiden:
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)
Codeblock B (Pseudocode)
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.
quelle
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.
quelle
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 :
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.
quelle
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:
Wenn Ihr ID-Feld spärlich ist, möchten Sie möglicherweise eine separate Liste von IDs abrufen und diese durchlaufen:
quelle
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.
quelle
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.
quelle