Wissen

Praxis-Artikel und Buchkapitel zu SQL-Performance, Sicherheit und Hochverfügbarkeit – alle frei verfügbar.

Beratung

Festpreis-Analyse mit Bericht und Handlungsempfehlung – oder strategische Begleitung bei Architektur, Migration und Hochverfügbarkeit.

Fachbücher

Die fünfbändige Reihe „Ulis SQL-Bibliothek“ – Band 1 verfügbar. Leseprobe herunterladen!

Tools

UB.SimSQL: SQL-Server-Lastsimulator mit regelbasierten Konfigurationsempfehlungen. Lokal, ohne Cloud, ohne Abo.

Schulungen

Online-Workshops zu Performance, Sicherheit und Entwicklung – kompakt, hands-on, ohne MOC-Folienschlacht.

Locking und Blocking aus Entwicklersicht: – SQL Server Performance

von

Dieser Artikel ist ein Kapitel aus:
SQL Server Performance & Troubleshooting
Praxisleitfaden, ca. 600 Seiten

[ Hier bei Amazon bestellen ]
[ Mehr zum Buch ]

Locking und Blocking aus Entwicklersicht:

Was dein Code mit den Locks macht — und was die Locks mit deinen Nutzern machen

Warum Entwickler Locking verstehen müssen

Der Ablauf ist immer gleich: Der DBA meldet sich, zeigt einen Screenshot von sys.dm_exec_requests und fragt — höflich, aber mit einem Unterton der nichts Gutes verheißt — ob du dir mal anschauen könntest, was Stored Procedure usp_Checkout da treibt. Session 73 hält seit 47 Sekunden einen X-Lock auf der Lager-Tabelle. Sieben andere Sessions warten.

Die Diagnose ist die Aufgabe des DBAs. Die Ursache liegt fast immer im Code. Kapitel 14 hat Blocking und Deadlocks aus der Administratoren-Perspektive beleuchtet: Wie man sie erkennt, wie man den Deadlock-Graph liest, welche DMVs die relevanten Informationen liefern. Dieses Kapitel wechselt die Perspektive: Was kann der Entwickler konkret tun, damit Blocking gar nicht erst entsteht?

Die häufigsten Blocking-Ursachen sind keine Konfigurationsprobleme — kein falsches Isolation Level, kein fehlkonfigurierter Lock-Manager. Es sind Code-Probleme: Transaktionen die zu lang offen bleiben, Zugriffsreihenfolgen die Deadlocks einladen, Massenupdates die Lock-Eskalation triggern.

 

Definition: Lock-Typen in Kürze

S-Lock (Shared): Wird beim Lesen gesetzt. Mehrere Sessions können gleichzeitig S-Locks halten. Kompatibel mit anderen S-Locks, inkompatibel mit X.

X-Lock (Exclusive): Wird beim Schreiben gesetzt. Kein anderer darf gleichzeitig S- oder X-Lock auf dieselbe Ressource halten.

U-Lock (Update): Zwischenstufe beim Lesen-vor-Update. Verhindert das klassische U→X-Deadlock-Muster. Ausführlich in Kapitel 14.

Blocking: Entsteht, wenn Session A auf einen Lock wartet, den Session B hält. Kein Fehler, sondern Warten — bis der Lock freigegeben wird oder ein Timeout greift.

 

Transaktion offen lassen — der Klassiker

Das häufigste Blocking-Muster in Web-Applikationen ist so simpel, dass es fast schmerzt: Eine Transaktion wird geöffnet, Daten werden gelesen, der Nutzer bekommt einen Bestätigungsdialog — und dann wartet der Code auf die Nutzerantwort. Mit offener Transaktion. Mit aktiven Locks.

Wenn der Nutzer kurz auf Kaffee ist, hält deine Transaktion X-Locks auf der Lager-Tabelle. Wenn er eine lange Mittagspause einlegt, blockierst du die gesamte Tabelle für alle anderen Sessions. Der DBA sieht LCK_M_X in sys.dm_exec_requests. Der Nutzer sieht "Laden…" im Browser. Du bekommst eine Mail.

-- FALSCH: Transaktion über Benutzereingabe geöffnet
-- X-Lock auf Lager-Zeile hält, solange der Nutzer "nachdenkt"
BEGIN TRANSACTION;

 

  -- Lagerbestand lesen und für den Nutzer anzeigen
  SELECT @Bestand = Bestand
  FROM Lager
  WHERE ArtikelID = @ArtikelID;

 

  -- *** Hier wartet das UI auf Nutzer-Bestätigung ***
  -- *** Lock ist aktiv während der gesamten Wartezeit ***

 

  IF @NutzerBestaetigt = 1
    UPDATE Lager
    SET Bestand = Bestand - @Menge
    WHERE ArtikelID = @ArtikelID;

 

COMMIT;

Die korrekte Lösung ist konzeptionell einfach: Daten holen ohne Transaktion, Nutzerentscheidung abwarten, dann eine kurze Transaktion nur für den eigentlichen Schreibvorgang öffnen. Der kritische Unterschied: Die Transaktion läuft in Millisekunden, nicht in Minuten.

-- RICHTIG: Transaktion nur für den Schreibvorgang

 

-- Schritt 1: Lesen OHNE Transaktion — kein dauerhafter Lock
SELECT @Bestand = Bestand
FROM Lager
WHERE ArtikelID = @ArtikelID;

 

-- Schritt 2: UI anzeigen, auf Nutzer-Eingabe warten
-- (Keine offene Transaktion, keine aktiven Locks)

 

-- Schritt 3: Kurze Transaktion nur für das Schreiben
BEGIN TRANSACTION;

 

  -- Optimistisch prüfen: Hat sich der Bestand geändert?
  UPDATE Lager
  SET Bestand = Bestand - @Menge
  WHERE ArtikelID = @ArtikelID
    AND Bestand >= @Menge;  -- Nur, wenn noch genug da ist

 

  IF @@ROWCOUNT = 0
  BEGIN
    ROLLBACK;
    -- Fehler zurückgeben: Bestand hat sich geändert
    RAISERROR('Bestand nicht mehr verfügbar.', 16, 1);
    RETURN;
  END

 

COMMIT;
-- Transaktion lief in < 5 ms. Kein Blocking.

 

Tipp: Externe Calls raus aus der Transaktion

Dieselbe Logik gilt für HTTP-Aufrufe, E-Mail-Versand und Datei-Operationen: Alles davon gehört außerhalb der Datenbank-Transaktion.

Wenn dein Payment-Service 3 Sekunden braucht, hältst du 3 Sekunden lang Locks. Wenn er einen Timeout wirft, bleibt die Transaktion offen bis der Connection-Timeout greift.

Reihenfolge: Transaktion committen, dann externe Aktionen ausführen. Nicht umgekehrt.

 

Optimistic vs. Pessimistic Locking: Die strategische Wahl

Hinter jedem Datenzugriffsmuster steckt eine implizite Entscheidung: Wie wahrscheinlich ist es, dass jemand anderes genau jetzt dieselbe Zeile ändert? Die Antwort bestimmt, welches Locking-Muster sinnvoll ist.

Pessimistisches Locking: Reservieren, bevor es jemand anderes tut

Pessimistisches Locking reserviert die Ressource beim Lesen — bevor du sie änderst. Das geschieht über den Lock Hint UPDLOCK: SQL Server setzt beim SELECT sofort einen U-Lock statt eines S-Locks. Kein anderer kann diesen Datensatz ändern, bis du fertig bist.

-- Pessimistisches Locking: Zeile beim Lesen sperren
-- UPDLOCK: U-Lock beim SELECT → verhindert konkurrierende Updates
-- HOLDLOCK: Lock bis Transaktionsende halten (= SERIALIZABLE für diese Zeile)
BEGIN TRANSACTION;

 

  SELECT @Kontostand = Kontostand
  FROM Konten WITH (UPDLOCK, HOLDLOCK)
  WHERE KontoID = @KontoID;

 

  -- Kein anderer kann dieses Konto jetzt ändern
  -- Geschäftslogik berechnen...

 

  UPDATE Konten
  SET Kontostand = @NeuerBetrag
  WHERE KontoID = @KontoID;

 

COMMIT;
-- Sicher, aber: Blocking bei hoher Parallelität

Pessimistisches Locking ist richtig, wenn Konflikte häufig sind, wenn die Folge eines verlorenen Updates schwerwiegend ist (Finanztransaktionen, Bestandsbuchungen), oder, wenn wenige Sessions gleichzeitig dieselben Datensätze bearbeiten. Der Preis ist reduzierte Parallelität.

Optimistisches Locking: Erst prüfen, dann schreiben

Optimistisches Locking geht davon aus, dass Konflikte selten sind. Du liest ohne Lock, führst deine Logik aus, und prüfst beim Schreiben ob sich die Zeile seit dem Lesen geändert hat. Wenn ja: Konflikt melden und Nutzer entscheiden lassen. Wenn nein: Schreiben.

SQL Server bietet dafür den Datentyp ROWVERSION (früher TIMESTAMP). Jedes UPDATE auf einer Zeile erhöht den ROWVERSION-Wert automatisch — serverseits, ohne Entwicklercode. Du liest den Wert beim SELECT, gibst ihn beim UPDATE als Bedingung mit, und siehst an @@ROWCOUNT ob jemand zwischendurch geändert hat.

-- Schema: ROWVERSION-Spalte als Konflikt-Detektor
-- ALTER TABLE Artikel ADD RowVers ROWVERSION;

 

-- Schritt 1: Lesen inkl. ROWVERSION — ohne Transaktion
SELECT
    @Preis   = Preis,
    @RowVers = RowVers
FROM Artikel
WHERE ArtikelID = @ArtikelID;

 

-- Schritt 2: Geschäftslogik ausführen, UI anzeigen...
-- (Keine Transaktion offen)

 

-- Schritt 3: Optimistisches Update
BEGIN TRANSACTION;

 

  UPDATE Artikel
  SET Preis = @NeuerPreis
  WHERE ArtikelID = @ArtikelID
    AND RowVers = @RowVers;  -- Wurde die Zeile geändert?

 

  IF @@ROWCOUNT = 0
  BEGIN
    ROLLBACK;
    -- Konflikt: Jemand hat den Artikel seit unserem Lesen geändert
    -- Applikation muss entscheiden: Überschreiben? Neu laden? Fehler?
    RAISERROR('Konflikt: Datensatz wurde durch andere Sitzung geändert.', 16, 1);
    RETURN;
  END

 

COMMIT;
-- Kein dauerhafter Lock auf dem Artikel — hohe Parallelität möglich

Optimistisches Locking ist richtig, wenn Konflikte selten sind, wenn hohe Parallelität wichtiger ist als maximale Konsistenz, oder, wenn Nutzer ohnehin mit Merge-Konflikten umgehen können (wie in vielen CRM- oder Content-Management-Systemen). Der Preis ist Konfliktbehandlung im Applikationscode.

 

Kriterium

Pessimistisch

Optimistisch

Konflikt-Wahrscheinlichkeit

Hoch — Reservierung sinnvoll

Niedrig — Prüfung beim Schreiben reicht

Parallelität

Niedrig — Locks blockieren andere

Hoch — kein dauerhafter Lock

Implementierungsaufwand

Gering — UPDLOCK/HOLDLOCK

Mittel — ROWVERSION-Spalte, Konflikt-Handler

Typischer Anwendungsfall

Finanz-Buchungen, Lagerverwaltung

CRM, Content-Management, Kataloge

Risiko bei Fehler

Blocking / Deadlocks bei hoher Last

Konflikte müssen explizit behandelt werden

Pessimistisches vs. Optimistisches Locking: Entscheidungsmatrix

 

Lock Hints: Das Werkzeug für spezielle Fälle

Lock Hints sind kein Allheilmittel — aber für bestimmte Situationen das richtige Werkzeug. Sie erlauben es, das Standard-Locking-Verhalten von SQL Server für einzelne Tabellenzugriffe zu übersteuern.

 

Hint

Wirkung

Wann sinnvoll

Risiko

UPDLOCK

U-Lock beim SELECT statt S-Lock

Lesen, bevor Update folgt — verhindert Deadlock-Muster U→X

Reduziert Parallelität beim Lesen

HOLDLOCK

S-Lock bis Transaktionsende halten

Wiederholbare Lesevorgänge benötigt (= REPEATABLE READ)

Erhöht Blocking-Risiko erheblich

ROWLOCK

Zeilen-Lock erzwingen

Lock-Eskalation zu Table Lock verhindern

Mehr Lock-Objekte, mehr Speicher

PAGLOCK

Seiten-Lock statt Zeilen-Lock

Bulk-Operationen auf vielen Zeilen einer Seite

Gröbere Granularität, mehr Blocking

TABLOCK

Table Lock sofort setzen

Bulk Insert, Truncate-Ersatz, volle Kontrolle

Blockiert alle anderen Sessions komplett

NOLOCK

Gar keine Locks setzen (Dirty Reads)

Kaum — Monitoring-Queries als Notlösung

Liest uncommittete und phantom-Daten — falsche Ergebnisse möglich

Lock Hints: Übersicht, Einsatz und Risiken

 

 

Warnung: NOLOCK ist keine Lösung

WITH (NOLOCK) ist verlockend: Keine Locks, kein Blocking, alles läuft schnell. Das stimmt — bis du merkst, dass du uncommittete Daten liest, Zeilen siehst die kurz danach gerollt werden, oder Zeilen komplett verpasst, weil eine Seiten-Reorganisation läuft.

NOLOCK löst das Blocking-Problem nicht — es umgeht es. Die Ursache (zu lange Transaktionen) bleibt. Und du bezahlst mit falschen Ergebnissen in deiner Applikation.

Alternative: RCSI (Read Committed Snapshot Isolation) gibt dir konsistente Lesevorgänge ohne Blocking. Kapitel 27 erklärt die Isolation Levels, Kapitel 29 erklärt RCSI im Detail.

 

Applikationsmuster für minimales Locking

Alles vorbereiten, dann schnell committen

Die Faustregel: Alles was außerhalb der Datenbank berechnet werden kann, wird außerhalb berechnet. Parameter validieren, Lookup-Werte vorab lesen, Geschäftslogik ausführen — und dann eine kurze Transaktion öffnen die nur noch das atomare Schreiben erledigt. Millisekunden, nicht Sekunden.

Read-before-write außerhalb der Transaktion

Das Muster aus dem Transaktion-Beispiel oben ist eine allgemeine Regel: Lies vor der Transaktion, schreibe in der Transaktion. Der einzige Fall für Lesen innerhalb einer Transaktion ist, wenn du einen konsistenten Snapshot mehrerer Werte brauchst, die sich nicht einzeln prüfen lassen.

Batch-Updates statt Zeile-für-Zeile

Wenn du 100.000 Zeilen aktualisieren musst, tue es nicht in einer einzigen Transaktion. Ab etwa 5.000 gesperrten Zeilen eskaliert SQL Server automatisch auf einen Table Lock — und blockiert alle anderen Sessions für die gesamte Laufzeit des Updates. Batches von 1.000 bis 2.000 Zeilen mit kurzen Pausen dazwischen halten die Lock-Granularität niedrig.

-- Batch-Update: Kein Table Lock, andere Sessions bekommen Luft
DECLARE @BatchSize INT = 1000;
DECLARE @Betroffene INT = 1;

 

WHILE @Betroffene > 0
BEGIN
    -- Kleine Transaktion: max. 1.000 Zeilen auf einmal
    UPDATE TOP (@BatchSize) Artikel
    SET Preis = Preis * 1.05         -- 5% Preiserhöhung
    WHERE Kategorie = 'Elektronik'
      AND Aktualisiert = 0;

 

    SET @Betroffene = @@ROWCOUNT;

 

    -- Markierung damit wir nicht dieselben Zeilen nochmals treffen
    -- (Hier vereinfacht — in Produktion: Status-Spalte oder Timestamp)

 

    -- 50ms Pause: Andere Sessions können in diesem Zeitfenster schreiben
    IF @Betroffene > 0
        WAITFOR DELAY '00:00:00.050';
END

 

Praxisbeispiel: Musterwerk GmbH: Checkout hält die Lager-Tabelle als Geisel

Bei Musterwerk GmbH (MWSQL01) meldeten Nutzer sporadisch langsame Checkout-Prozesse. Die Analyse mit sys.dm_exec_requests zeigte LCK_M_X Waits mit bis zu 38 Sekunden Wartezeit.

Ursache: usp_Checkout öffnete eine Transaktion beim Laden des Warenkorbs, las den Lagerbestand, und wartete dann auf die Zahlungsbestätigung vom Payment-Gateway (durchschnittlich 4–8 Sekunden). X-Lock auf der Lager-Zeile hielt während der gesamten Zahlungsphase.

Fix: Transaktion erst nach Zahlungsbestätigung öffnen. Lagerbestand ohne Transaktion lesen, Payment-Gateway außerhalb aufrufen, dann atomare Transaktion nur für Bestandsreduzierung und Bestellanlage. Blocking-Wartezeiten: von 38 Sekunden auf unter 50ms. Kapitel 32 zeigt den vollständigen Fall.

 

Deadlock-Prävention im Code

Deadlocks sind keine Serverprobleme — sie sind fast immer Code-Probleme. SQL Server löst sie zuverlässig durch Rollback der billigsten Session (dem Deadlock Victim), aber das kostet Arbeit und hinterlässt Fehlermeldungen. Besser: Von vornherein vermeiden.

Tabellen immer in gleicher Reihenfolge zugreifen

Das ist die wichtigste Deadlock-Präventions-Regel. Wenn Prozedur A immer zuerst Kunden sperrt und dann Bestellungen, und Prozedur B genau das Gleiche tut — gibt es keinen zyklischen Konflikt. Deadlocks entstehen, wenn A und B die Reihenfolge vertauschen. Definiere eine kanonische Zugriffsreihenfolge für alle Tabellen im System und halte sie konsequent ein.

-- FALSCH: Inkonsistente Reihenfolge — lädt Deadlocks ein
-- Prozedur A: erst Kunden → dann Bestellungen
BEGIN TRANSACTION;
    UPDATE Kunden SET Umsatz += @Betrag WHERE KundeID = @KID;
    UPDATE Bestellungen SET Status = N'abgeschlossen' WHERE BestellID = @BID;
COMMIT;

 

-- Prozedur B (gleichzeitig): erst Bestellungen → dann Kunden
BEGIN TRANSACTION;
    UPDATE Bestellungen SET Gesamtbetrag = @Betrag WHERE BestellID = @BID;
    UPDATE Kunden SET LetzteBestellung = GETDATE() WHERE KundeID = @KID;
COMMIT;
-- A hält Lock auf Kunden, wartet auf Bestellungen
-- B hält Lock auf Bestellungen, wartet auf Kunden
-- → Deadlock. Error 1205 für eines der beiden.

 

-- RICHTIG: Beide Prozeduren in kanonischer Reihenfolge
-- Regel: immer zuerst Kunden, dann Bestellungen
BEGIN TRANSACTION;
    UPDATE Kunden SET Umsatz += @Betrag, LetzteBestellung = GETDATE()
    WHERE KundeID = @KID;
    UPDATE Bestellungen SET Status = N'abgeschlossen', Gesamtbetrag = @Betrag
    WHERE BestellID = @BID;
COMMIT;

UPDLOCK beim ersten Zugriff, wenn später Update folgt

Das U→X-Deadlock-Muster ist ein Klassiker: Zwei Sessions lesen dieselbe Zeile mit S-Lock, dann will jede ihren S-Lock zu einem X-Lock eskalieren. Beide warten aufeinander — Deadlock. UPDLOCK beim initialen SELECT bricht das Muster: U-Locks sind kompatibel zum Lesen, aber inkompatibel zueinander. Eine Session bekommt den Lock, die andere wartet — Blocking statt Deadlock, und Blocking löst sich auf.

Retry-Logik für Deadlock Victim Error 1205

Wenn ein Deadlock trotz aller Prävention auftritt, rollt SQL Server eine der beteiligten Sessions zurück (Deadlock Victim) und wirft Error 1205. Das ist kein fataler Fehler — es ist ein transienter Zustand der retry-fähig ist. Applikationscode sollte das behandeln.

// .NET Retry-Pattern für Deadlock Victim (Error 1205)
// Maximal 3 Versuche mit exponentialem Backoff
//
// int maxRetries = 3;
// int attempt = 0;
// bool success = false;
//
// while (!success && attempt < maxRetries)
// {
//     attempt++;
//     try
//     {
//         await ExecuteTransactionAsync();
//         success = true;
//     }
//     catch (SqlException ex) when (ex.Number == 1205)
//     {
//         if (attempt >= maxRetries) throw;
//         // Exponential Backoff: 50ms, 100ms, 200ms...
//         await Task.Delay(50 * (int)Math.Pow(2, attempt - 1));
//     }
// }

 

-- In T-SQL: Deadlock in TRY/CATCH fangen
BEGIN TRY
    BEGIN TRANSACTION;
        UPDATE Konten SET Kontostand -= @Betrag WHERE KontoID = @Von;
        UPDATE Konten SET Kontostand += @Betrag WHERE KontoID = @Nach;
    COMMIT;
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0 ROLLBACK;
    -- Error 1205 = Deadlock Victim
    -- Caller muss Retry-Logik implementieren
    THROW;
END CATCH

Diagnose: Locking-Probleme identifizieren und beheben

 

Hinweis: Symptome

Nutzer berichten über "hängende" Aktionen — Buttons die nichts tun, Ladebalken die nicht enden.

Checkout- oder Bestellprozesse blockieren andere Nutzer zur gleichen Tageszeit (typisch: Stoßzeiten).

Deadlock-Fehler in der Applikation: "Transaction (Process ID X) was deadlocked" — Error 1205 in den Logs.

LCK_M_X oder LCK_M_U tauchen in sys.dm_exec_requests auf, wait_time > 1.000 ms.

Monitoring zeigt hohe blocking_session_id-Werte — viele Sessions mit demselben Blocker.

 

 

Tipp: So misst du das

Offene Transaktionen mit Alter identifizieren:

SELECT s.session_id, s.login_name,

DATEDIFF(s, t.transaction_begin_time, GETDATE()) AS AlterSekunden,

s.status, r.wait_type, r.wait_time / 1000.0 AS WaitSek

FROM sys.dm_tran_active_transactions t

JOIN sys.dm_exec_sessions s ON t.transaction_id = s.transaction_id

LEFT JOIN sys.dm_exec_requests r ON s.session_id = r.session_id

WHERE DATEDIFF(s, t.transaction_begin_time, GETDATE()) > 5

ORDER BY AlterSekunden DESC;

 

Blocking-Ketten identifizieren:

SELECT blocking_session_id AS Blocker, session_id AS Waiter,

wait_type, wait_time / 1000.0 AS WarteSekunden

FROM sys.dm_exec_requests

WHERE blocking_session_id > 0

ORDER BY wait_time DESC;

 

 

Warnung: Typische Fehlinterpretationen

"Kurze Queries können nicht blocken." — Doch. Eine Query die 5ms läuft, aber in einer Transaktion die seit 45 Sekunden offen ist, hält die Locks für 45 Sekunden.

"NOLOCK löst unser Blocking-Problem." — Temporär, ja. Es umgeht das Problem und schafft neue: Dirty Reads, Phantom Reads, inkonsistente Ergebnisse. Die Ursache bleibt.

"Deadlocks sind Serverprobleme, der DBA soll das fixen." — Nein. Der DBA kann Deadlocks erkennen und melden. Beheben kann sie nur der Entwickler — durch konsistente Zugriffsreihenfolgen und kürzere Transaktionen.

"Wir nutzen SERIALIZABLE, deshalb haben wir keine Probleme." — SERIALIZABLE löst Anomalie-Probleme, erzeugt aber massives Blocking bei hoher Parallelität. Kein Isolation Level ersetzt gutes Transaktionsdesign.

 

 

Tipp: Erste Gegenmaßnahmen

1. Offene Transaktionen identifizieren: Die Query im "So misst du das"-Kasten zeigt alle Transaktionen die älter als 5 Sekunden sind. Eine Transaktion die 30+ Sekunden alt ist und X-Locks hält ist fast immer der Übeltäter.

2. Transaktion-Scope im Code einschränken: BEGIN TRANSACTION so spät wie möglich, COMMIT so früh wie möglich. Reads vor die Transaktion ziehen.

3. Retry-Logik für Error 1205 implementieren: Wenn noch keine vorhanden ist, sofort nachrüsten. Drei Versuche mit exponentiellem Backoff reichen für 95% der Fälle.

4. Deadlock-Trace aktivieren: Extended Events Session auf dem Server einrichten (Kapitel 8 zeigt wie), um alle Deadlocks mit Graph zu loggen. Ohne Graph ist Deadlock-Analyse Raterei.

 

Zusammenfassung

Blocking entsteht im Code, nicht im Server. SQL Server sperrt korrekt — der Applikationscode hält die Locks zu lange, in der falschen Granularität, oder in der falschen Reihenfolge.

Die wichtigsten Prinzipien: Transaktionen minimal halten — öffnen so spät wie möglich, committen so früh wie möglich. Keine Benutzereingaben und keine externen Calls innerhalb von Transaktionen. Optimistisches Locking mit ROWVERSION für Szenarien mit seltenen Konflikten. Pessimistisches Locking mit UPDLOCK/HOLDLOCK, wenn Konflikte häufig sind und Konsistenz kritisch ist. Tabellen immer in kanonischer Reihenfolge zugreifen, um Deadlocks zu vermeiden. Retry-Logik für Error 1205 in jedem Applikationscode der Transaktionen verwendet.

Die vier Diagnose-Kästen dieses Kapitels geben dir den schnellen Einstieg, wenn es brennt: Symptome erkennen, Transaktion-Alter messen, Fehlinterpretationen vermeiden, sofort handeln.

 

Ausblick auf Kapitel 29: Was ist, wenn gutes Transaktionsdesign allein nicht reicht — weil Leser und Schreiber sich strukturell blockieren, selbst mit kurzen Transaktionen? Row Versioning und Snapshot Isolation lösen dieses Problem auf Architekturebene: Leser blockieren keine Schreiber, Schreiber blockieren keine Leser. SQL Server hält dafür Zeilenversionen in TempDB vor. Was das kostet und wann es sich lohnt, zeigt Kapitel 29.

 

Abb. 1: Transaktionsscope und Lock-Lebensdauer

 

Kapitel 29