SQL Server Deadlocks analysieren und beheben
Vereinzelte Deadlocks sind Alltag — Wellen sind ein Konstruktionsfehler
Deadlock-Wellen analysieren und beheben
Vereinzelte Deadlocks sind Alltag — Wellen sind ein Konstruktionsfehler. So liest du den Graph und findest die Ursache.
Es ist Dienstagvormittag, 10:47 Uhr. Bei der Musterwerk GmbH läuft seit dem Wochenende die neue Version der Lagerverwaltung, und seitdem hagelt es Fehlermeldungen: Transaction (Process ID 84) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Nicht einmal, nicht zweimal — das Monitoring zählt 340 Stück seit Mitternacht. Der Hersteller der Anwendung sagt, das sei „ein Datenbankproblem“. Der Hoster sagt, der Server sei „grün“. Und der Fachbereich sagt gar nichts mehr, weil jede dritte Buchung mit einem Fehler abbricht.
Willkommen bei den Deadlock-Wellen. Ein einzelner Deadlock pro Tag ist kein Drama — SQL Server ist dafür gebaut, ihn sauber aufzulösen. Aber Wellen von Deadlocks sind nie Pech und nie Zufall: Sie sind ein strukturelles Problem im Zusammenspiel von Code, Indizes und Sperren, und sie lassen sich systematisch diagnostizieren. Dieser Beitrag aus dem SQL-Server-Notarztwagen (die Triage-Übersicht liegt unter /sql-server-notarztwagen/) zeigt dir, wo die Deadlock-Daten längst auf dich warten, wie du den Graph liest — und welche fünf Muster hinter fast allen Wellen stecken.
Was bedeutet Deadlock technisch?
Ein Deadlock ist eine zirkuläre Verklemmung: Session A hält eine Sperre auf Ressource 1 und will zusätzlich Ressource 2. Session B hält die Sperre auf Ressource 2 und will Ressource 1. Beide warten aufeinander, keiner kommt je ans Ziel — der Kreis ist geschlossen. Ohne Eingriff stünden beide Sessions bis zum Jüngsten Tag. Deshalb läuft im SQL Server der Deadlock-Monitor: Er prüft standardmäßig alle fünf Sekunden den Wartegraphen, erkennt geschlossene Kreise und löst sie auf — indem er eine der beteiligten Sessions opfert.
Das Opfer — im Jargon das Deadlock Victim — bekommt Error 1205, seine Transaktion wird zurückgerollt, und die Sperren werden freigegeben, damit der andere Prozess weiterarbeiten kann. Gewählt wird im Normalfall die Session mit den geringsten Rollback-Kosten — also die, deren bisherige Änderungen am billigsten rückgängig zu machen sind. Mit SET DEADLOCK_PRIORITY lässt sich diese Wahl beeinflussen: Eine Session mit niedrigerer Priorität wird bevorzugt geopfert. Das ist gelegentlich nützlich (ein nächtlicher Wartungsjob darf gern das Opfer sein, die Kassenbuchung nicht), aber kein Heilmittel — es verschiebt nur, wer den Schaden hat.
Die wichtigste Abgrenzung gleich vorweg, weil sie in jeder zweiten Eskalations-Telko durcheinandergeht: Blocking ist kein Deadlock. Beim Blocking wartet eine Session auf eine andere — unangenehm, aber endlich: Sobald der Blocker committet, geht es weiter. Beim Deadlock warten zwei (oder mehr) Sessions gegenseitig aufeinander, und ohne den Monitor ginge es nie weiter. Praktische Folge: Blocking erzeugt Langsamkeit, Deadlocks erzeugen Fehler. Wenn deine Anwendung Timeouts wirft, schau in den Blocking-Beitrag des Notarztwagens. Wenn sie Error 1205 wirft, bist du hier richtig.
|
Abgrenzung: Deadlock vs. Blocking Deadlock — zirkuläres Warten, löst sich nie von selbst. Der Deadlock-Monitor opfert eine Session, die Anwendung sieht Error 1205. Symptom: Fehler. |
|---|
Der Diagnose-Pfad: den Deadlock-Graph lesen
Das zentrale Beweismittel ist der Deadlock-Graph — eine XML-Aufzeichnung, die der Server bei jedem Deadlock erstellt. Sie enthält die beteiligten Prozesse samt ihrer Statements, die umkämpften Ressourcen und die Owner-/Waiter-Beziehungen. Die folgende Skizze zeigt das häufigste Szenario: zwei Sessions, zwei Tabellen, gegenläufige Zugriffsreihenfolge.

Beim Lesen interessieren dich drei Fragen, immer in dieser Reihenfolge: Erstens, welche Statements sind beteiligt (im XML der inputbuf-Block je Prozess)? Zweitens, welche Ressourcen werden umkämpft — Key Lock, Page Lock, ganze Objekte? Drittens, in welcher Reihenfolge haben die Prozesse zugegriffen? Bei Wellen lohnt der Blick auf zehn, zwanzig Graphen nebeneinander: Wiederholen sich dieselben Statements und Ressourcen, hast du dein Muster — und damit den Hebel.
Erste Diagnose: konkrete Skripte
Die beste Nachricht des Tages: Du musst nichts nachträglich aktivieren. Die immer laufende system_health-Session zeichnet jeden Deadlock als xml_deadlock_report auf. Das folgende Skript holt die Graphen der letzten Zeit aus dem Ring Buffer:
|
— Deadlock-Graphen aus der system_health-Session ziehen SELECT CONVERT(xml, event_data).value( '(event/@timestamp)[1]', 'datetime2') AS Zeitpunkt, CONVERT(xml, event_data).query( '/event/data/value/deadlock') AS DeadlockGraph FROM sys.fn_xe_file_target_read_file( 'system_health*.xel', NULL, NULL, NULL) WHERE object_name = 'xml_deadlock_report' ORDER BY Zeitpunkt DESC; |
|---|
Klicke in SSMS auf das XML, speichere es als .xdl und öffne die Datei erneut in SSMS — dann bekommst du die grafische Darstellung mit Prozessen, Ressourcen und dem blauen Kreuz auf dem Victim. Verdächtig ist alles, was sich wiederholt: dieselben zwei Prozeduren, dieselbe Tabelle, derselbe Index. Eine Welle hat fast immer ein dominantes Muster — finde es, statt jeden Deadlock einzeln zu betrauern.
Die system_health rolliert allerdings: Bei viel Betrieb sind ältere Ereignisse schnell verdrängt. Für die Analyse einer laufenden Welle — und als dauerhaftes Frühwarnsystem — richtest du eine eigene XE-Session mit File-Target ein:
|
— Dedizierte Deadlock-Session mit File-Target CREATE EVENT SESSION [DeadlockCapture] ON SERVER ADD EVENT sqlserver.xml_deadlock_report ADD TARGET package0.event_file ( SET filename = N'DeadlockCapture', max_file_size = 50, max_rollover_files = 4 ) WITH (STARTUP_STATE = ON); GO ALTER EVENT SESSION [DeadlockCapture] ON SERVER STATE = START; |
|---|
Drittes Werkzeug für den Blick aufs große Ganze: die Sperren-Landschaft während der Welle. Häufen sich exklusive Sperren auf einem bestimmten Objekt, weißt du, wo der Kampfplatz liegt — und ob womöglich Lock Escalation im Spiel ist:
|
— Wer haelt welche Sperren auf welchen Objekten? SELECT tl.request_session_id AS SPID, DB_NAME(tl.resource_database_id) AS Datenbank, tl.resource_type, — KEY, PAGE, OBJECT … tl.request_mode, — S, U, X, IX … tl.request_status, — GRANT oder WAIT OBJECT_NAME(p.object_id) AS Objekt FROM sys.dm_tran_locks AS tl LEFT JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id WHERE tl.resource_database_id = DB_ID() ORDER BY tl.request_session_id; |
|---|
Die häufigsten Ursachen — und was du dagegen tust
1. Gegenläufige Zugriffsreihenfolge
Der Vater aller Deadlocks: Prozedur A aktualisiert erst Auftrag, dann Kunde — Prozedur B macht es genau andersherum. Läuft beides parallel oft genug, kracht es zwangsläufig; die Skizze oben zeigt exakt dieses Muster. Die Lösung ist unspektakulär und gerade deshalb so wirksam: eine verbindliche Zugriffsreihenfolge im gesamten Code. Tabellen werden in allen Transaktionen in derselben Reihenfolge angefasst — alphabetisch, nach fachlicher Hierarchie, völlig egal, Hauptsache konsistent. Das ist keine Datenbankeinstellung, sondern eine Code-Konvention. Wenn der Hersteller der Anwendung also sagt, das sei „ein Datenbankproblem“: Der Deadlock-Graph mit den beiden gegenläufigen inputbuf-Blöcken ist dein freundliches Gegenargument.
2. Fehlende Indizes — Scans verbreitern den Kampfplatz
Ein UPDATE mit WHERE-Klausel ohne passenden Index muss die Tabelle scannen — und sperrt dabei weit mehr Zeilen, als es eigentlich ändern will. Je breiter der Scan, desto größer die Wahrscheinlichkeit, dass sich zwei Prozesse in die Quere kommen, die fachlich gar nichts miteinander zu tun haben. Im Deadlock-Graph erkennst du das Muster an Page Locks statt Key Locks und an erstaunlich „unbeteiligten“ Statements. Die Lösung ist ein gezielter Index auf die Filterspalten — danach sperrt das UPDATE nur noch die Zeilen, die es wirklich braucht. Nebenwirkung: Die Query wird auch noch schneller. Es gibt schlechtere Deals.
3. Lock Escalation — aus Nadelstichen wird ein Vorschlaghammer
Hält eine Transaktion zu viele Einzelsperren auf einem Objekt — die Faustregel liegt bei etwa 5.000 —, eskaliert SQL Server auf eine Tabellensperre. Aus chirurgischen Key Locks wird ein Vorschlaghammer, der die komplette Tabelle blockiert, und plötzlich kollidiert jeder mit jedem. Typischer Auslöser sind Massen-Updates und -Deletes ohne Batching: das nächtliche „lösche alles älter als 90 Tage“ in einer einzigen Transaktion. Gegenmittel Nummer eins ist Batching — in Häppchen von ein paar tausend Zeilen arbeiten, zwischendurch committen. ALTER TABLE … SET (LOCK_ESCALATION = DISABLE) existiert auch, kuriert aber nur das Symptom und treibt den Sperren-Speicherverbrauch hoch — Ausnahmewerkzeug, kein Standard.
4. Key-Lookup-Deadlocks — wenn zwei Indizes sich verkeilen
Das subtilste Muster, weil hier ein einzelnes SELECT und ein einzelnes UPDATE auf derselben Tabelle reichen: Das SELECT liest über den Nonclustered Index und springt per Key Lookup in den Clustered Index. Das UPDATE ändert zuerst den Clustered Index und muss dann den Nonclustered Index nachziehen. Zwei Wege durch dieselbe Tabelle, gegenläufige Reihenfolge — Verklemmung. Im Graph verrät sich das Muster dadurch, dass beide Ressourcen Indizes derselben Tabelle sind. Die robusteste Lösung: den Nonclustered Index per INCLUDE um die Spalten erweitern, die das SELECT braucht — als Covering Index entfällt der Lookup, und der zweite Weg verschwindet komplett.
5. Lange Transaktionen und der falsche Isolation Level
Je länger eine Transaktion ihre Sperren hält, desto größer das Zeitfenster für Verklemmungen. Klassiker: Anwendungscode, der mitten in einer offenen Transaktion auf externe Systeme wartet — oder schlimmer, auf Benutzereingaben. Transaktionen gehören kurz und knackig: Daten vorher sammeln, dann öffnen, schreiben, committen. Auf der Sperren-Seite hilft zusätzlich Read Committed Snapshot Isolation (RCSI): Leser arbeiten gegen Zeilenversionen aus der tempdb und blockieren Schreiber nicht mehr — Reader-Writer-Deadlocks verschwinden damit praktisch vollständig. Writer-Writer-Verklemmungen (Ursachen 1 bis 3) löst RCSI dagegen nicht — die zwei gegenläufigen UPDATEs aus der Skizze verkeilen sich auch im Snapshot-Modus.
|
— Ist RCSI schon aktiv? SELECT name, is_read_committed_snapshot_on FROM sys.databases WHERE name = N'MeineDB';
— RCSI aktivieren (braucht kurz exklusiven Zugriff auf die DB) ALTER DATABASE [MeineDB] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE; |
|---|
Vor dem Aktivieren zwei Hausaufgaben: Die tempdb muss den Version Store verkraften — wie du sie dafür aufstellst, steht im Konfigurations-Beitrag der GRÜN-Stufe. Und die Anwendung sollte einen kurzen Test durchlaufen, weil sich die Lese-Semantik ändert: Du liest ab sofort den letzten committeten Stand, statt auf laufende Änderungen zu warten. Für 99 Prozent aller Anwendungen ist das genau das, was sie ohnehin erwarten.
Was du auf keinen Fall tun solltest
|
Die drei häufigsten Anfängerfehler 1. Jeden Deadlock einzeln analysieren statt das Muster der Welle zu suchen. Zwanzig Graphen mit demselben Statement-Paar sind ein Befund — nicht zwanzig Befunde. |
|---|
Nachsorge: Damit es nicht wiederkommt
Nach der akuten Welle drei strukturelle Maßnahmen: Erstens, die DeadlockCapture-Session dauerhaft laufen lassen — mit File-Target und Rollover kostet sie praktisch nichts und liefert beim nächsten Vorfall die komplette Historie statt der rollierten Reste der system_health. Zweitens, eine Deadlock-Baseline ins Monitoring aufnehmen: Wie viele 1205er pro Tag sind bei euch normal? Ein Anstieg von drei auf dreißig fällt dann auf, bevor der Fachbereich anruft. Drittens, die Zugriffsreihenfolge als Code-Konvention festschreiben und im Review prüfen — die billigste Deadlock-Prävention der Welt steht nicht in der Datenbank, sondern im Entwickler-Wiki. Wo Deadlocks in der Gesamtdiagnose einzuordnen sind, zeigt übrigens das Diagnose-Schema im Beitrag zur GELB-Stufe „SQL Server läuft langsam“.
Vertiefung in der Buchreihe
Sperren, Isolation Level und der Version Store füllen in der Buchreihe „SQL Server in der Praxis“ eigene Kapitel — die Übersicht aller Bände findest du auf der SQL-Pillar-Seite unter /sql-server/.
|
Empfohlene Bände Band 1: Performance & Troubleshooting — Sperr-Architektur im Detail: Lock-Modi, Lock Escalation, Isolation Level, Version Store und die Wait Stats, mit denen du Sperrkonflikte im Gesamtbild erkennst. |
|---|
Verwandte Notfälle im Notarztwagen
Deadlocks kommen selten ohne Begleitsymptome. Diese Beiträge aus dem Cluster lohnen den Blick:
Drei Wege, wenn die Deadlock-Welle jetzt gerade rollt
Festpreis-Analyse
Die Welle kommt jede Nacht wieder, der Hersteller zeigt auf die Datenbank, und intern fehlt die Zeit für die Graph-Archäologie? Dafür gibt es die Festpreis-Analyse: Ich werte die Deadlock-Graphen aus, identifiziere die Muster, prüfe Indizes und Isolation-Einstellungen und liefere einen Bericht mit priorisierten Maßnahmen — inklusive der Argumentationshilfe für das Gespräch mit dem Anwendungshersteller. Zum vereinbarten Festpreis, ohne Stundenzähler.
Akut-Hotline
Wenn die Welle gerade jetzt die Produktion lahmlegt: anrufen. Halte den Zugriff auf die system_health-Daten bereit und die Information, was sich zuletzt geändert hat — Release, Datenwachstum, neuer Report. Mit zwei, drei Graphen vor Augen ist das dominante Muster meist in unter einer Stunde identifiziert.
Selbst-Diagnose-Kit
Du willst die Welle selbst sezieren? Sehr gut. Das Diagnose-Kit bündelt die Skripte aus diesem Beitrag — system_health-Auswertung, DeadlockCapture-Session, Sperren-Analyse, RCSI-Check — als ausführbares Paket mit Auswertungshilfe und einer Muster-Checkliste für die fünf häufigsten Ursachen.
|
Ein letzter Tipp Führe das system_health-Skript aus diesem Beitrag einmal auf deinem Produktionsserver aus — heute, ohne akuten Anlass. Die meisten DBAs sind ehrlich überrascht, wie viele Deadlocks ihr Server still und leise wegmoderiert, ohne dass je jemand hingeschaut hat. Drei pro Woche? Alles gut. Dreihundert? Dann hast du gerade eine Welle gefunden, von der nur deine Anwender wussten. |
|---|
Häufige Fragen (FAQ)
Diese Sektion ist auf häufige Suchanfragen abgestimmt. Sie hilft dir, schnell den Punkt zu finden, der dir gerade akut weiterhilft — und sie ist gleichzeitig die Eingabe für das FAQPage-Schema auf der Seite.
Was ist ein Deadlock im SQL Server?
Ein Deadlock ist eine zirkuläre Verklemmung: Session A hält eine Sperre und wartet auf eine Ressource, die Session B hält — und B wartet gleichzeitig auf die Ressource von A. Keiner kann weitermachen, keiner gibt freiwillig auf. Der Deadlock-Monitor des SQL Servers erkennt den Kreis, wählt ein Opfer (das mit den geringsten Rollback-Kosten), rollt dessen Transaktion zurück und schickt ihm Error 1205. Der Unterschied zum Blocking: Blocking löst sich von selbst auf, sobald der Blocker fertig ist — ein Deadlock nie. Die saubere Abgrenzung steht im Technik-Abschnitt dieses Beitrags.
Wie analysiere ich einen Deadlock-Graph?
Im Graph (XML oder grafisch in SSMS) suchst du drei Dinge: die beteiligten Prozesse mit ihren Statements, die umkämpften Ressourcen (Key Lock, Page Lock, Objekt) und die Pfeilrichtungen — wer hält (Owner), wer wartet (Waiter). Bildet sich ein geschlossener Kreis, hast du die Verklemmung vor Augen. Entscheidend ist dann die Zugriffsreihenfolge: Greifen beide Prozesse auf dieselben Tabellen in umgekehrter Reihenfolge zu? Das ist in der Praxis die häufigste Ursache. Die kommentierte Beispiel-Skizze findest du im Abschnitt zum Diagnose-Pfad.
Welcher Isolation Level vermeidet Deadlocks?
Kein Isolation Level eliminiert Deadlocks vollständig — aber Read Committed Snapshot Isolation (RCSI) entschärft die häufigste Klasse: Leser blockieren keine Schreiber mehr und umgekehrt, weil Leser gegen eine Zeilenversion aus dem Version Store lesen. Reader-Writer-Deadlocks verschwinden damit weitgehend. Writer-Writer-Deadlocks bleiben allerdings bestehen — zwei UPDATEs in gegenläufiger Reihenfolge verklemmen sich auch unter RCSI. Höhere Level wie Serializable verschärfen das Problem eher, weil sie mehr und längere Sperren halten. Details und das Aktivierungs-Skript stehen im RCSI-Abschnitt.
Wie aktiviere ich Deadlock-Logging mit Extended Events?
Die gute Nachricht: Es läuft schon. Die immer aktive system_health-Session zeichnet jeden Deadlock als xml_deadlock_report auf — du musst die Daten nur abfragen, das Skript dazu steht im Diagnose-Abschnitt dieses Beitrags. Für längere Aufbewahrung oder gezielte Analyse einer Deadlock-Welle lohnt eine eigene XE-Session auf das Event xml_deadlock_report mit File-Target: Die system_health rolliert nämlich, und bei viel Betrieb sind ältere Deadlocks schnell rausgealtert. Trace Flag 1222 ins ERRORLOG geht auch noch, ist aber die Methode von vorgestern.
Was bedeutet Lock Escalation?
SQL Server sperrt normalerweise feingranular auf Zeilen- oder Seitenebene. Hält eine Transaktion aber zu viele Einzelsperren (Faustregel: ab etwa 5.000 auf einem Objekt), eskaliert der Server auf eine einzige Tabellensperre — das spart Verwaltungsaufwand, sperrt aber schlagartig die komplette Tabelle. Genau solche Eskalationen verwandeln harmlose Parallelität in Deadlock-Wellen, weil plötzlich jeder mit jedem kollidiert. Typischer Auslöser: Massen-Updates ohne Batching oder Scans wegen fehlender Indizes. Gegenmittel: kleinere Batches, passende Indizes — und nur im begründeten Ausnahmefall ALTER TABLE mit LOCK_ESCALATION = DISABLE.
Wann sollte ich Read Committed Snapshot Isolation verwenden?
RCSI ist für die meisten OLTP-Datenbanken eine sinnvolle Standardeinstellung — Azure SQL Database fährt sie ab Werk. Sinnvoll immer dann, wenn Reader-Writer-Konflikte dein Problem sind: Berichte, die Schreiber blockieren, oder Deadlocks zwischen SELECT und UPDATE. Zwei Dinge vorher prüfen: Erstens braucht RCSI Platz im Version Store der tempdb — die muss das verkraften. Zweitens ändert sich die Lese-Semantik: Du liest den letzten committeten Stand statt auf den aktuellen zu warten. Anwendungen, die sich auf Sperr-Verhalten als Logik verlassen (selten, aber es gibt sie), müssen getestet werden.
Wie verhindere ich Deadlocks zwischen Indizes?
Der Klassiker ist der Key-Lookup-Deadlock: Ein SELECT liest über den Nonclustered Index und will dann per Lookup in den Clustered Index — während ein UPDATE genau andersherum erst den Clustered Index ändert und dann den Nonclustered Index nachziehen muss. Zwei Wege hinein, gegenläufige Reihenfolge, Verklemmung. Die robusteste Lösung: den Nonclustered Index per INCLUDE zum Covering Index machen, dann entfällt der Lookup komplett. Alternativ entschärft RCSI die Lese-Seite. Wie du das Muster im Deadlock-Graph erkennst, zeigt der Ursachen-Abschnitt dieses Beitrags.
