Xenosystems Logo
Sascha Hess

Diplom-Biologe | Senior IT-Consultant

SH

Sascha Hess

xenosystems.de - IT-Consulting & Data Management

www.xenosystems.de

 

 

NOTFALL-KIT – SQL SERVER 2026

 

SQL Server

 

 

 

Datenbankfundamente

 

Transaktionssicherheit, Isolation und Konsistenz für produktive SQL-Server-Umgebungen

WAS SIE IN DIESEM KIT ERHALTEN:

 

1

ACID vollständig verstehen

Atomicity, Consistency, Isolation, Durability — mit konkreten SQL-Beispielen

 

 

2

10 Transaktions-Fallen

Typische Fehler in produktiven Systemen — mit sofortigen Korrekturen

 

 

3

Isolationsstufen im Alltag

Read Committed, Snapshot, Serializable — wann welche Stufe, warum

 

 

4

Deadlock-Diagnose & -Prävention

Deadlocks erkennen, analysieren und dauerhaft vermeiden

 

 

5

30-Tage-Stabilitätsplan

Von inkonsistenten Daten und Blocking zu robuster Transaktionsarchitektur

 


 

 

Rechtliche Hinweise und Haftungsausschluss

 

HAFTUNGSAUSSCHLUSS

 

Alle Skripte und Empfehlungen wurden sorgfältig erarbeitet. Änderungen an Transaktions- und Isolationskonfigurationen in Produktivdatenbanken können weitreichende Auswirkungen auf Performance, Nebenläufigkeit und Datenkonsistenz haben. Der Autor übernimmt keinerlei Haftung für Datenverlust, Systemausfälle oder Inkonsistenzen. Testen Sie alle Änderungen ausschließlich in einer dedizierten Testumgebung mit produktionsnahen Daten und Lastprofilen.

 

KEINE ERGEBNISGARANTIE

 

Genannte Performance-Gewinne und Stabilitätsverbesserungen sind Erfahrungswerte aus realen KMU-Umgebungen. Tatsächliche Ergebnisse hängen von Hardware, SQL Server Version, Datenvolumen, Abfragemustern und Anwendungsarchitektur ab.

 

VERSIONSHINWEIS

 

Die Inhalte beziehen sich auf SQL Server 2022 (16.x) und SQL Server 2025 (17.x) sowie Azure SQL Database, Stand März 2026. Durch künftige Updates von Microsoft können Abweichungen entstehen.

 

URHEBERRECHT

 

Dieses Dokument ist für den persönlichen oder betriebsinternen Gebrauch des Käufers lizenziert. Weiterverkauf, Weitergabe an Dritte und öffentliche Veröffentlichung sind ohne schriftliche Genehmigung nicht gestattet.

 

KEINE VERBINDUNG ZU MICROSOFT

 

Dieses Kit ist ein unabhängiges Werk und steht in keinerlei Verbindung zur Microsoft Corporation. SQL Server ist eine eingetragene Marke der Microsoft Corporation.

 

Eine ausführliche Version dieses Haftungsausschlusses befindet sich am Ende dieses Dokuments.

 


 

 

Inhaltsverzeichnis

 

 

01  Einleitung

Warum ACID das unsichtbare Fundament jeder produktiven Datenbank ist

 

02  ACID vollständig verstehen

Atomicity, Consistency, Isolation, Durability — mit Praxisbeispielen

 

03  Transaktionen in SQL Server

BEGIN, COMMIT, ROLLBACK, Savepoints und verschachtelte Transaktionen

 

04  Die 10 Transaktions-Fallen

Typische Fehler in produktiven Systemen — mit Sofort-Korrekturen

 

05  Isolationsstufen im Detail

Read Uncommitted bis Serializable — Wahl, Konsequenzen, Empfehlungen

 

06  Locking und Blocking

Sperren verstehen, Blocking erkennen, Wartezeiten reduzieren

 

07  Deadlocks

Erkennen, analysieren, dauerhaft verhindern

 

08  Optimistic Concurrency — RCSI und Snapshot

Sperrkonflikte ohne Blocking lösen

 

09  Transaktionen in ERP und Anwendungsarchitektur

Lange Transaktionen, Connection Pooling, Retry-Logik

 

10  30-Tage-Stabilitätsplan

Von Blocking und Inkonsistenzen zur robusten Transaktionsarchitektur

 

 


 

 

01

Einleitung

 

Warum ACID das unsichtbare Fundament jeder produktiven Datenbank ist

 

In mittelständischen Unternehmen laufen täglich Tausende Transaktionen durch das ERP-System: Aufträge werden erfasst, Lagerbestände gebucht, Zahlungen verbucht, Rechnungen erstellt. Solange alles läuft, denkt niemand über Transaktionen nach. Erst wenn etwas schiefgeht, wird sichtbar, wie wichtig das Fundament ist.

 

  Ein Lohnlauf startet, bricht nach 800 von 1.200 Mitarbeitern ab — und niemand weiß, welche Gehälter bereits gebucht sind und welche nicht.

 

  Zwei Sachbearbeiter bearbeiten gleichzeitig denselben Auftrag — eine Änderung überschreibt die andere, ohne dass jemand gewarnt wird.

 

  Ein Reporting-Tool liest Daten aus einer Tabelle, während ein ETL-Prozess gerade Daten importiert — der Bericht zeigt halbfertige Datensätze.

 

  Ein Datenbankserver startet nach einem Stromausfall neu — aber waren die letzten 47 Buchungen committed oder nicht?

 

All diese Szenarien werden durch ACID-Garantien adressiert. ACID — Atomicity, Consistency, Isolation, Durability — ist kein akademisches Konzept. Es ist die Summe der Zusagen, die SQL Server jedem Datenbanknutzer gibt: entweder passiert alles, oder nichts. Entweder ist ein Zustand konsistent, oder die Datenbank lässt ihn nicht zu. Entweder sind zwei gleichzeitige Operationen voneinander isoliert, oder ihr Zusammenspiel ist klar definiert. Und einmal bestätigte Daten überleben jeden Absturz.

 

Dieses Kit erklärt diese Garantien nicht nur theoretisch, sondern zeigt, wie sie in SQL Server technisch umgesetzt sind, wo sie in KMU-Umgebungen typischerweise verletzt werden, und wie Sie die häufigsten Probleme erkennen und dauerhaft beheben.

 

 

WANN ACID-PROBLEME SICHTBAR WERDEN

  Daten, die "irgendwie nicht stimmen" — ohne erkennbaren Anlass und ohne Fehlermeldung.

  Berichte, die zu verschiedenen Zeitpunkten verschiedene Ergebnisse liefern — ohne Datenänderung.

  Gelegentliche Deadlock-Fehler in der Anwendung — die "einfach passieren".

  Lange Ladezeiten, die andere Benutzer blockieren — PAGELATCH oder LCK_M-Waits.

  Nach einem Serverabsturz: Unsicherheit darüber, welche Daten wirklich gesichert sind.

 


 

 

02

ACID vollständig verstehen

 

Atomicity, Consistency, Isolation, Durability — mit konkreten Praxisbeispielen

 

2.1 Atomicity — Alles oder Nichts

 

Eine Transaktion ist eine unteilbare Einheit. Entweder werden alle Operationen einer Transaktion ausgeführt und committed, oder — bei Fehler oder explizitem ROLLBACK — keine einzige. Es gibt keinen halbfertigen Zustand.

 

Praxisbeispiel Lagerabbuchung:

 

-- Ohne Atomicity: zwei getrennte Statements — gefährlich

UPDATE dbo.Lagerbestand SET Menge = Menge - 10 WHERE ArtikelNr = 'A001';

-- ← Server-Absturz HIER: Menge wurde reduziert, Buchung fehlt

INSERT INTO dbo.Lagerbuchungen (ArtikelNr, Menge, BuchungsTyp) VALUES ('A001', -10, 'ABGANG');

 

-- Mit Atomicity: Transaktion sichert Alles-oder-Nichts

BEGIN TRANSACTION;

    UPDATE dbo.Lagerbestand SET Menge = Menge - 10 WHERE ArtikelNr = 'A001';

    INSERT INTO dbo.Lagerbuchungen (ArtikelNr, Menge, BuchungsTyp) VALUES ('A001', -10, 'ABGANG');

COMMIT TRANSACTION;

-- Server-Absturz zwischen UPDATE und INSERT → beim Neustart: ROLLBACK beider Operationen

-- Kein halbfertiger Zustand, keine Datenbankinkonsistenz

 

 

Wie SQL Server Atomicity implementiert: Jede Änderung wird zunächst in das Write-Ahead Log (WAL/Transaction Log) geschrieben — bevor sie auf die Datenseite kommt. Bei Absturz liest der Recovery-Prozess beim Neustart das Log und rollt alle uncommitted Transaktionen zurück.

 

2.2 Consistency — Regeln bleiben immer erhalten

 

Die Datenbank wechselt immer von einem konsistenten Zustand in den nächsten. Keine Transaktion darf Integritätsregeln verletzen — CHECK-Constraints, FOREIGN KEY-Constraints, UNIQUE-Constraints und benutzerdefinierte Regeln gelten immer, nicht nur bei Einzeloperationen.

 

-- Consistency: Constraint-Verletzung macht gesamte Transaktion ungültig

BEGIN TRANSACTION;

 

    -- Erster INSERT: gültig

    INSERT INTO dbo.Auftraege (AuftragsNr, KundenNr, Betrag)

    VALUES (1001, 42, 5000.00);

 

    -- Zweiter INSERT: verletzt FK-Constraint (KundenNr 9999 existiert nicht)

    INSERT INTO dbo.Auftragspositionen (AuftragsNr, ArtikelNr, KundenNr, Menge)

    VALUES (1001, 'X001', 9999, 5);

    -- Fehler: FOREIGN KEY constraint violation

    -- → gesamte Transaktion ist ungültig, ROLLBACK beider Inserts

 

COMMIT TRANSACTION;  -- wird nie erreicht

 

-- Consistency-Check: welche Constraints gibt es?

SELECT

    t.name                  AS Tabelle,

    c.name                  AS Constraint_Name,

    c.type_desc             AS Constraint_Typ,

    OBJECT_DEFINITION(c.object_id) AS Definition

FROM sys.objects c

JOIN sys.tables t ON c.parent_object_id = t.object_id

WHERE c.type IN ('C','F','UQ','PK')   -- Check, FK, Unique, PK

ORDER BY t.name, c.type_desc;

 

 

2.3 Isolation — Gleichzeitige Transaktionen stören sich nicht

 

Isolation garantiert, dass parallel laufende Transaktionen sich gegenseitig nicht in inkonsistente Zustände versetzen. Wie stark diese Isolation ist, steuern die Isolationsstufen (Kapitel 5). Die schwächste Isolation lässt Probleme zu, die stärkste verhindert alle — auf Kosten der Performance.

 

Die drei klassischen Isolationsprobleme:

 

-- Problem 1: Dirty Read — Lesen uncommitteter Daten

-- Session A: ändert, committed noch nicht

BEGIN TRANSACTION;

UPDATE dbo.Lagerbestand SET Menge = 0 WHERE ArtikelNr = 'A001';

-- ← noch kein COMMIT

 

-- Session B (Isolationsstufe READ UNCOMMITTED): liest die 0 — obwohl noch nicht final

SELECT Menge FROM dbo.Lagerbestand WHERE ArtikelNr = 'A001';

-- Ergebnis: 0 — falsch! Session A rollt vielleicht noch zurück.

 

-- Problem 2: Non-Repeatable Read — dieselbe Zeile, verschiedene Werte

-- Session A:

BEGIN TRANSACTION;

SELECT Preis FROM dbo.Artikel WHERE ArtikelNr = 'A001';  -- liest 100,00

-- ← Session B ändert und committed Preis auf 120,00

SELECT Preis FROM dbo.Artikel WHERE ArtikelNr = 'A001';  -- liest jetzt 120,00

-- Dieselbe Abfrage, gleiche Transaktion, verschiedene Ergebnisse

COMMIT;

 

-- Problem 3: Phantom Read — neue Zeilen erscheinen innerhalb einer Transaktion

-- Session A:

BEGIN TRANSACTION;

SELECT COUNT(*) FROM dbo.Auftraege WHERE KundenNr = 42;  -- ergibt 5

-- ← Session B fügt neuen Auftrag für Kunde 42 ein und committed

SELECT COUNT(*) FROM dbo.Auftraege WHERE KundenNr = 42;  -- ergibt jetzt 6

-- "Phantom"-Zeile ist aufgetaucht

COMMIT;

 

 

2.4 DurabilityCommitted ist für immer

 

Eine committed Transaktion überlebt jeden Absturz — Stromausfall, OS-Crash, Hardware-Fehler. SQL Server garantiert dies durch das Write-Ahead Logging: Committed Daten sind im Transaction Log auf Disk persistent, bevor der COMMIT-Befehl erfolgreich zurückgemeldet wird.

 

-- Durability-Diagnostik: ist das Transaction Log gesund?

SELECT

    db.name                             AS Datenbank,

    log_size_mb = ls.cntr_value / 1024.0,

    log_used_mb = lu.cntr_value / 1024.0,

    log_used_pct = ROUND(lu.cntr_value * 100.0 / NULLIF(ls.cntr_value,0), 1),

    -- Zu hoher Log-Verbrauch = Durability-Risiko (Log läuft voll)

    CASE

        WHEN lu.cntr_value * 100.0 / NULLIF(ls.cntr_value,0) > 80 THEN 'KRITISCH'

        WHEN lu.cntr_value * 100.0 / NULLIF(ls.cntr_value,0) > 60 THEN 'WARNUNG'

        ELSE 'OK'

    END                                 AS Status

FROM sys.databases db

JOIN sys.dm_os_performance_counters ls

    ON ls.instance_name = db.name AND ls.counter_name = 'Log File(s) Size (KB)'

JOIN sys.dm_os_performance_counters lu

    ON lu.instance_name = db.name AND lu.counter_name = 'Log File(s) Used Size (KB)'

WHERE db.database_id > 4   -- keine Systemdatenbanken

ORDER BY log_used_pct DESC;

 

 


 

 

03

Transaktionen in SQL Server

 

BEGIN, COMMIT, ROLLBACK, Savepoints und verschachtelte Transaktionen

 

3.1 Transaktionssteuerung — die vollständige Syntax

 

-- Explizite Transaktion: Standard für alle DML-Operationen in Stored Procedures

BEGIN TRANSACTION;                        -- Transaktion starten

    -- DML-Operationen

    UPDATE ...;

    INSERT ...;

    DELETE ...;

COMMIT TRANSACTION;                       -- Permanent auf Disk schreiben

 

-- Oder bei Fehler:

ROLLBACK TRANSACTION;                     -- Alle Änderungen zurückrollen

 

-- Transaktion benennen (empfohlen für Klarheit in verschachtelten Szenarien)

BEGIN TRANSACTION Auftrags_Buchung;

    -- ...

COMMIT TRANSACTION Auftrags_Buchung;

 

-- @@TRANCOUNT: Verschachtelungstiefe der aktuellen Transaktion

-- 0 = keine aktive Transaktion

-- 1 = eine äußere Transaktion

-- 2+ = verschachtelt (nur äußerstes COMMIT wirkt wirklich)

SELECT @@TRANCOUNT AS Transaktionstiefe;

 

 

3.2 Das robuste Transaktions-Template

 

-- Produktionsreifes Template: explizite Steuerung, XACT_STATE(), strukturiertes Logging

CREATE OR ALTER PROCEDURE dbo.SP_Auftrag_Komplett_Buchen

    @AuftragsNr     INT,

    @BenutzerID     NVARCHAR(100)

AS

BEGIN

    SET NOCOUNT ON;

    SET XACT_ABORT OFF;   -- Wir steuern ROLLBACK selbst via TRY/CATCH

 

    DECLARE @Fehler     NVARCHAR(MAX);

    DECLARE @ProtoID    INT;

 

    -- Protokoll öffnen

    INSERT INTO dbo.Buchungs_Protokoll (AuftragsNr, Benutzer, Start, Status)

    VALUES (@AuftragsNr, @BenutzerID, SYSUTCDATETIME(), 'LAUFEND');

    SET @ProtoID = SCOPE_IDENTITY();

 

    BEGIN TRY

        BEGIN TRANSACTION;

 

            -- Schritt 1: Auftragsstatus prüfen

            IF NOT EXISTS (SELECT 1 FROM dbo.Auftraege

                           WHERE AuftragsNr = @AuftragsNr AND Status = 'FREIGEGEBEN')

                RAISERROR('Auftrag nicht freigegeben oder nicht vorhanden.', 16, 1);

 

            -- Schritt 2: Lagerabbuchung

            UPDATE dbo.Lagerbestand

            SET Menge = Menge - p.Menge

            FROM dbo.Lagerbestand lb

            JOIN dbo.Auftragspositionen p ON lb.ArtikelNr = p.ArtikelNr

            WHERE p.AuftragsNr = @AuftragsNr;

 

            -- Schritt 3: Negativen Bestand verhindern

            IF EXISTS (SELECT 1 FROM dbo.Lagerbestand WHERE Menge < 0)

                RAISERROR('Lagerbestand würde negativ — Buchung abgebrochen.', 16, 1);

 

            -- Schritt 4: Auftragsstatus auf GEBUCHT setzen

            UPDATE dbo.Auftraege

            SET Status = 'GEBUCHT', GebuchtAm = SYSUTCDATETIME(),

                GebuchtVon = @BenutzerID

            WHERE AuftragsNr = @AuftragsNr;

 

            -- Schritt 5: Audit-Log

            INSERT INTO dbo.AuditLog (Tabelle, Aktion, ObjektID, Benutzer, Zeitpunkt)

            VALUES ('Auftraege', 'BUCHEN', @AuftragsNr, @BenutzerID, SYSUTCDATETIME());

 

        COMMIT TRANSACTION;

 

        -- Protokoll schließen: Erfolg

        UPDATE dbo.Buchungs_Protokoll

        SET Ende = SYSUTCDATETIME(), Status = 'ERFOLG'

        WHERE ProtoID = @ProtoID;

 

    END TRY

    BEGIN CATCH

        -- XACT_STATE():

        --  1 = Transaktion aktiv und committbar → ROLLBACK

        -- -1 = Transaktion aktiv, nicht committbar (Batch-Abbruch) → ROLLBACK

        --  0 = Keine aktive Transaktion → kein ROLLBACK nötig

        IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;

 

        SET @Fehler = ERROR_MESSAGE();

 

        -- Protokoll schließen: Fehler

        UPDATE dbo.Buchungs_Protokoll

        SET Ende = SYSUTCDATETIME(), Status = 'FEHLER', Fehlermeldung = @Fehler

        WHERE ProtoID = @ProtoID;

 

        -- Fehler nach oben weitergeben

        THROW;

    END CATCH

END

 

 

3.3 Savepoints — teilweises Rollback innerhalb einer Transaktion

 

-- Savepoints: Rücksprungpunkte innerhalb einer Transaktion

-- Nützlich wenn einzelne Schritte optional sind, die Haupttransaktion aber weiterlaufen soll

BEGIN TRANSACTION;

 

    -- Pflichtschritt: muss gelingen

    UPDATE dbo.Auftraege SET Status = 'GEBUCHT' WHERE AuftragsNr = 1001;

 

    SAVE TRANSACTION Vor_Benachrichtigung;   -- Savepoint setzen

 

    BEGIN TRY

        -- Optionaler Schritt: E-Mail-Eintrag in Queue

        INSERT INTO dbo.Mail_Queue (Empfaenger, Betreff, Body)

        VALUES ('kunde@example.com', 'Auftragsbestätigung', 'Ihr Auftrag 1001 wurde gebucht.');

    END TRY

    BEGIN CATCH

        -- Nur bis zum Savepoint zurückrollen — Haupttransaktion bleibt aktiv

        ROLLBACK TRANSACTION Vor_Benachrichtigung;

        -- Fehler loggen, aber nicht werfen — Mail war optional

        INSERT INTO dbo.Fehlerprotokoll (Meldung) VALUES (ERROR_MESSAGE());

    END CATCH

 

    -- Haupttransaktion wird committed — auch wenn Mail-Schritt fehlschlug

    INSERT INTO dbo.Lagerbuchungen (AuftragsNr, Zeitpunkt) VALUES (1001, SYSUTCDATETIME());

 

COMMIT TRANSACTION;

 

 

3.4 SET XACT_ABORT — automatisches Rollback bei Fehler

 

-- SET XACT_ABORT ON: jeder Laufzeitfehler rollt die gesamte Transaktion zurück

-- Empfohlen wenn kein TRY/CATCH vorhanden ist — z.B. in einfachen Skripten

SET XACT_ABORT ON;

BEGIN TRANSACTION;

    UPDATE dbo.Kunden SET Umsatzklasse = 'A' WHERE KundenNr = 42;

    -- Wenn dieses Statement fehlschlägt → automatisch ROLLBACK der gesamten Transaktion

    UPDATE dbo.Kunden SET Umsatzklasse = 'B' WHERE KundenNr = 43;

COMMIT TRANSACTION;

SET XACT_ABORT OFF;

 

-- WICHTIG: SET XACT_ABORT ON und TRY/CATCH zusammen brauchen XACT_STATE()-Prüfung

-- weil XACT_ABORT die Transaktion in Zustand -1 (nicht committbar) versetzen kann

-- → immer XACT_STATE() prüfen, niemals blind ROLLBACK aufrufen

 

 


 

 

04

Die 10 Transaktions-Fallen

 

Typische Fehler in produktiven Systemen — mit sofortigen Korrekturen

 

01 Transaktion ohne TRY/CATCH — halbfertige Daten bei Fehlern

 

Ein DML-Skript beginnt mit BEGIN TRANSACTION, führt zehn Updates durch, und der zehnte schlägt fehl. Ohne TRY/CATCH bleibt die Transaktion offen — die Verbindung trennt sich, der Rollback erfolgt implizit. Oder schlimmer: das Skript geht weiter, der COMMIT wird erreicht, neun von zehn Updates sind permanent.

 

LÖSUNG:

 

  Jede explizite Transaktion bekommt TRY/CATCH mit ROLLBACK und THROW.

  SET XACT_ABORT ON als minimale Absicherung für Skripte ohne TRY/CATCH.

  Nach jeder DML-Operation @@ROWCOUNT prüfen — wenn 0 wo > 0 erwartet wird, RAISERROR.

 

-- Anti-Pattern: kein Fehler-Handling

BEGIN TRANSACTION;

UPDATE dbo.Konten SET Saldo = Saldo - 1000 WHERE KontoNr = 1;

UPDATE dbo.Konten SET Saldo = Saldo + 1000 WHERE KontoNr = 2; -- wenn dies scheitert?

COMMIT;  -- wird trotzdem ausgeführt wenn kein Abbruch-Signal

 

-- Richtig: TRY/CATCH + XACT_STATE()

BEGIN TRY

    BEGIN TRANSACTION;

        UPDATE dbo.Konten SET Saldo = Saldo - 1000 WHERE KontoNr = 1;

        IF @@ROWCOUNT = 0 RAISERROR('Konto 1 nicht gefunden.', 16, 1);

        UPDATE dbo.Konten SET Saldo = Saldo + 1000 WHERE KontoNr = 2;

        IF @@ROWCOUNT = 0 RAISERROR('Konto 2 nicht gefunden.', 16, 1);

    COMMIT TRANSACTION;

END TRY

BEGIN CATCH

    IF XACT_STATE() <> 0 ROLLBACK TRANSACTION;

    THROW;

END CATCH

 

 

02 Lange Transaktionen — Blocking für alle anderen

 

Eine Transaktion öffnet um 09:00 Uhr, legt Sperren auf mehrere Tabellen, und bleibt bis 09:45 Uhr offen, weil eine Benutzeroberfläche wartet oder eine Schleife zu langsam ist. Alle anderen Benutzer, die dieselben Tabellen lesen oder schreiben wollen, werden für 45 Minuten blockiert.

 

LÖSUNG:

 

  Transaktionen so kurz wie möglich halten: Vorberechnungen, Lookups und Validierungen AUSSERHALB der Transaktion.

  Benutzerinteraktion niemals innerhalb einer offenen Transaktion — Transaktion öffnen, committen, dann dem Benutzer das Ergebnis zeigen.

  Lange Transaktionen durch Batch-Verarbeitung in Teilschritten ersetzen.

 

-- Anti-Pattern: Transaktion umschließt Benutzerabfrage oder langsame Schleife

BEGIN TRANSACTION;

    -- Langsame Vorbereitung innerhalb der Transaktion — hält Sperren

    SELECT @Preis = Preis FROM dbo.Artikel WHERE ArtikelNr = @Art;  -- Sperre!

    WAITFOR DELAY '00:00:30';  -- Simulation: Benutzer denkt nach

    UPDATE dbo.Auftraege SET Preis = @Preis WHERE AuftragsNr = @Nr;

COMMIT;

 

-- Richtig: Sperre nur während der eigentlichen Schreiboperation

-- Schritt 1: Daten lesen AUSSERHALB der Transaktion

SELECT @Preis = Preis FROM dbo.Artikel WHERE ArtikelNr = @Art;  -- keine Sperre

 

-- Schritt 2: Benutzer entscheidet (außerhalb der Transaktion)

-- ... Benutzer denkt nach ...

 

-- Schritt 3: Transaktion nur für den eigentlichen Schreibvorgang

BEGIN TRANSACTION;

    UPDATE dbo.Auftraege SET Preis = @Preis WHERE AuftragsNr = @Nr;

COMMIT;

-- Transaktionsdauer: Millisekunden statt Minuten

 

 

03 Implizite Transaktionen vergessen — IMPLICIT_TRANSACTIONS-Falle

 

Wenn SET IMPLICIT_TRANSACTIONS ON gesetzt ist, startet jede DML-Anweisung automatisch eine Transaktion — ohne BEGIN TRANSACTION. Ohne explizites COMMIT bleibt sie offen. In manchen ODBC-Treibern und älteren Anwendungen ist das die Standardeinstellung.

 

LÖSUNG:

 

  SET IMPLICIT_TRANSACTIONS auf allen Verbindungen prüfen und auf OFF setzen.

  Offene Transaktionen ohne expliziten COMMIT regelmäßig diagnostizieren.

 

-- Implicit Transactions diagnostizieren: offene Transaktionen erkennen

SELECT

    s.session_id,

    s.login_name,

    s.host_name,

    s.program_name,

    s.open_transaction_count,

    s.last_request_start_time,

    DATEDIFF(MINUTE, s.last_request_start_time, GETDATE()) AS Offen_Seit_Min,

    t.text AS Letzter_SQL_Text,

    -- Lange offene Transaktionen sind immer ein Problem

    CASE

        WHEN DATEDIFF(MINUTE, s.last_request_start_time, GETDATE()) > 30

        THEN 'KRITISCH: > 30 Min offen'

        WHEN DATEDIFF(MINUTE, s.last_request_start_time, GETDATE()) > 5

        THEN 'WARNUNG: > 5 Min offen'

        ELSE 'OK'

    END AS Bewertung

FROM sys.dm_exec_sessions s

CROSS APPLY sys.dm_exec_sql_text(s.most_recent_sql_handle) t

WHERE s.open_transaction_count > 0

  AND s.is_user_process = 1

ORDER BY Offen_Seit_Min DESC;

 

 

04 Rollback in verschachtelten Transaktionen missverständlich

 

Eine Stored Procedure öffnet eine Transaktion. Sie ruft eine weitere Procedure auf, die ebenfalls eine Transaktion öffnet — @@TRANCOUNT ist jetzt 2. Diese innere Procedure führt ROLLBACK TRANSACTION aus. In SQL Server rollt ROLLBACK immer die äußerste Transaktion zurück — nicht nur die innere. Das ist für viele Entwickler überraschend.

 

LÖSUNG:

 

  ROLLBACK TRANSACTION rollt immer alles zurück — es gibt kein "teilweises ROLLBACK" auf Transaktionsebene außer via Savepoints.

  Verschachtelte Procedures immer mit XACT_STATE() prüfen statt @@TRANCOUNT.

  Innere Procedures sollten niemals ROLLBACK aufrufen — nur THROW. Die äußerste Ebene entscheidet.

 

-- Die verschachtelte Transaktions-Falle

CREATE OR ALTER PROCEDURE dbo.SP_Inner AS

BEGIN

    SET NOCOUNT ON;

    -- @@TRANCOUNT ist bereits 1 (äußere Transaktion)

    BEGIN TRANSACTION;  -- @@TRANCOUNT wird 2

        UPDATE dbo.Tabelle SET Wert = 'X' WHERE ID = 1;

    -- Fehler tritt auf:

    ROLLBACK TRANSACTION;  -- rollt ALLES zurück, @@TRANCOUNT → 0 !

    -- Wenn die äußere Procedure jetzt COMMIT aufruft → Fehler: keine aktive Transaktion

END;

 

-- Richtig: innere Procedure wirft Fehler, äußere entscheidet

CREATE OR ALTER PROCEDURE dbo.SP_Inner_Korrekt AS

BEGIN

    SET NOCOUNT ON;

    BEGIN TRY

        -- kein eigenes BEGIN TRANSACTION in verschachtelter Procedure

        UPDATE dbo.Tabelle SET Wert = 'X' WHERE ID = 1;

        IF @@ROWCOUNT = 0 RAISERROR('Kein Datensatz gefunden.', 16, 1);

    END TRY

    BEGIN CATCH

        THROW;  -- Fehler nach oben — äußere Procedure entscheidet über ROLLBACK

    END CATCH

END;

 

 

05 Fehlende Indexe auf WHERE-Spalten in Transaktionen — unnötig breite Sperren

 

Eine UPDATE-Anweisung ohne Index auf der WHERE-Spalte führt zu einem Table Scan. SQL Server legt dabei eine Shared Lock auf jede gelesene Zeile und eine Exclusive Lock auf jede geänderte. Bei einem Table Scan sind das potenziell Millionen Locks — und damit ein massives Blocking für alle anderen Abfragen.

 

LÖSUNG:

 

  Alle WHERE-Spalten in UPDATE/DELETE-Statements auf Index-Coverage prüfen.

  Execution Plan analysieren: Table Scan in einer DML-Operation ist fast immer ein Blocking-Risiko.

  Selektive Updates: WHERE-Klausel so restriktiv wie möglich, um Lockumfang zu minimieren.

 

-- Diagnose: DML-Operationen mit Table Scans (potenzielle Blocking-Quellen)

SELECT TOP 10

    qs.total_elapsed_time / qs.execution_count / 1000  AS avg_ms,

    qs.execution_count,

    qs.total_logical_reads / qs.execution_count        AS avg_reads,

    SUBSTRING(st.text, 1, 300)                         AS QueryText,

    qp.query_plan

FROM sys.dm_exec_query_stats qs

CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st

CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp

WHERE st.text LIKE '%UPDATE%'

   OR st.text LIKE '%DELETE%'

ORDER BY qs.total_logical_reads DESC;

 

 

06 NOLOCK-Hinweis überall — Dirty Reads als Normalzustand

 

Ein Entwickler fügt WITH (NOLOCK) zu jeder Abfrage hinzu, "weil es schneller ist". Tatsächlich liest der Report nun gelegentlich Daten, die kurz danach zurückgerollt wurden — Phantomzeilen, doppelte Zeilen, fehlende Zeilen. Der Controller fragt sich, warum der Tagesumsatz im Bericht täglich um 0,3 % schwankt.

 

LÖSUNG:

 

  WITH (NOLOCK) niemals in Berichten über Finanzdaten, Lagerbestände oder andere kritische Kennzahlen.

  Stattdessen RCSI (Read Committed Snapshot Isolation) aktivieren — kein NOLOCK nötig, keine Dirty Reads, keine Sperren auf Leseanfragen (Kapitel 8).

  NOLOCK nur akzeptabel für echte "Ungefähr-Counts" ohne Genauigkeitsanspruch.

 

-- Anti-Pattern: NOLOCK auf Finanzdaten

SELECT SUM(Nettobetrag) AS Tagesumsatz

FROM dbo.Auftraege WITH (NOLOCK)   -- liest möglicherweise zurückgerollte Transaktionen!

WHERE CAST(Auftragsdatum AS DATE) = CAST(GETDATE() AS DATE);

 

-- Lösung 1: NOLOCK entfernen (funktioniert mit RCSI, Kapitel 8)

SELECT SUM(Nettobetrag) AS Tagesumsatz

FROM dbo.Auftraege

WHERE CAST(Auftragsdatum AS DATE) = CAST(GETDATE() AS DATE);

 

-- Lösung 2: Explizit Snapshot-Isolation für Lesekonsistenz

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

SELECT SUM(Nettobetrag) AS Tagesumsatz

FROM dbo.Auftraege

WHERE CAST(Auftragsdatum AS DATE) = CAST(GETDATE() AS DATE);

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

 

 

07 COMMIT vergessen — Verbindungs-Pooling hält Transaktion offen

 

Eine Anwendung mit Connection Pooling öffnet eine Transaktion, gibt die Verbindung dann ohne COMMIT oder ROLLBACK zurück in den Pool. Die Verbindung — und damit die offene Transaktion — landet beim nächsten Request. Der nächste Benutzer operiert innerhalb einer fremden Transaktion. Daten werden commited oder gerollt, die gar nicht dieser Sitzung gehören.

 

LÖSUNG:

 

  Anwendungscode-Regel: jeder Code-Pfad der eine Transaktion öffnet, muss garantiert COMMIT oder ROLLBACK erreichen — auch bei Exceptions.

  Connection-Pool-Überprüfung: SET XACT_ABORT ON am Anfang jeder Verbindung als Sicherheitsnetz.

  Monitoring: offene Transaktionen auf Pool-Verbindungen regelmäßig prüfen.

 

08 Transaktionslog läuft voll — Durability gefährdet

 

Das Transaktionslog einer 50-GB-Datenbank ist 200 GB groß und wächst weiter. Ursache: ein Log-Backup fehlt, oder eine lange Transaktion verhindert die Log-Truncation. Wenn das Log voll läuft, schlägt jede DML-Operation fehl — inklusive ROLLBACK. Die Datenbank ist faktisch ausgefallen.

 

LÖSUNG:

 

  Recovery Model prüfen: FULL erfordert regelmäßige Log-Backups, SIMPLE schneidet automatisch ab.

  Log-Backup-Job: mindestens stündlich für produktive Datenbanken, alle 15 Minuten für kritische.

  Log-Wachstum überwachen: Alert wenn Log > 70 % voll (aus Kapitel 2.4).

 

-- Log-Truncation verhindernde Faktoren identifizieren

SELECT

    db.name                 AS Datenbank,

    db.log_reuse_wait_desc  AS Warte_Auf,

    -- Was verhindert Log-Truncation?

    CASE db.log_reuse_wait_desc

        WHEN 'LOG_BACKUP'          THEN 'Log-Backup fehlt — sofort Log-Backup ausführen!'

        WHEN 'ACTIVE_TRANSACTION'  THEN 'Lange offene Transaktion — siehe sys.dm_exec_sessions'

        WHEN 'REPLICATION'         THEN 'Replikation liest Log — Replikations-Agent prüfen'

        WHEN 'DATABASE_MIRRORING'  THEN 'Spiegelung hängt — Mirror-Status prüfen'

        WHEN 'AVAILABILITY_REPLICA' THEN 'Always On Replica hängt — AG-Status prüfen'

        WHEN 'NOTHING'             THEN 'OK — Log kann beim nächsten Backup abgeschnitten werden'

        ELSE                            'Unbekannt — Microsoft Docs prüfen'

    END                     AS Massnahme

FROM sys.databases db

WHERE db.database_id > 4

ORDER BY db.log_reuse_wait_desc;

 

 

09 Batch-Deletes ohne Transaktionskontrolle — Log-Explosion

 

Ein Skript löscht 10 Millionen Zeilen in einer einzigen DELETE-Anweisung. SQL Server schreibt jede einzelne gelöschte Zeile ins Transaktionslog — das Log wächst um Gigabytes, die Transaktion dauert Stunden, und während dieser Zeit ist die Tabelle für andere blockiert.

 

LÖSUNG:

 

  Batch-Delete in Schleifen: maximal 5.000–10.000 Zeilen pro Transaktion.

  Kurze Pause zwischen Batches: andere Transaktionen kommen durch, Log-Backup kann zwischendurch arbeiten.

  Für sehr große Löschoperationen: Partitions-Switching als schnellste Methode (millisekunden).

 

-- Anti-Pattern: alles auf einmal

DELETE FROM dbo.Protokoll WHERE Datum < '2023-01-01';  -- 10 Millionen Zeilen, Stunden, Log-Explosion

 

-- Richtig: Batch-Delete mit kontrollierter Transaktionsgröße

DECLARE @Batch INT = 5000;

DECLARE @Geloescht INT = 1;

 

WHILE @Geloescht > 0

BEGIN

    DELETE TOP (@Batch) FROM dbo.Protokoll

    WHERE Datum < '2023-01-01';

    SET @Geloescht = @@ROWCOUNT;

 

    -- Kurze Pause: andere Transaktionen kommen durch

    IF @Geloescht = @Batch WAITFOR DELAY '00:00:01';

END

-- Laufzeit: identisch, aber keine Log-Explosion, kein Dauerblocken

 

 

10 Kein Monitoring offener Transaktionen — Probleme bleiben unsichtbar

 

Transaktionsprobleme — Blocking, lange offene Transaktionen, Deadlocks — werden erst gemeldet, wenn Benutzer sich beschweren. Bis dahin hat das Problem schon lange existiert. Ein passives System ohne Monitoring erkennt Probleme immer zu spät.

 

LÖSUNG:

 

  Täglicher Blocking-Report als SQL Agent Job — Ergebnis per Mail wenn Blocking > 60 Sekunden.

  Deadlock-Trace immer aktiv: Extended Events oder Trace Flag 1222.

 Transaction Log-Füllstand im Monitoring-Dashboard sichtbar machen.

 


 

 

05

Isolationsstufen im Detail

 

Read Uncommitted bis Serializable — Wahl, Konsequenzen und Empfehlungen

 

5.1 Die fünf Isolationsstufen im Vergleich

 

Isolationsstufe

Dirty Read

Non-Repeatable Read

Phantom Read

Performance

Empfehlung

READ UNCOMMITTED

Möglich

Möglich

Möglich

Maximal

Nie für Finanzdaten

READ COMMITTED

Verhindert

Möglich

Möglich

Hoch

Standard — aber mit RCSI

REPEATABLE READ

Verhindert

Verhindert

Möglich

Mittel

Selten nötig

SNAPSHOT

Verhindert

Verhindert

Verhindert

Hoch

Empfohlen für Leseanfragen

SERIALIZABLE

Verhindert

Verhindert

Verhindert

Niedrig

Nur für kritische Operationen

 

5.2 Isolationsstufe je Anwendungsfall setzen

 

-- READ UNCOMMITTED: nur für "Ungefähr-Zahlen" ohne Genauigkeitsanspruch

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT COUNT(*) AS Ungefaehre_Zeilenanzahl FROM dbo.Riesige_Tabelle;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

 

-- REPEATABLE READ: wenn innerhalb einer Transaktion dieselbe Zeile

-- mehrfach gelesen werden muss und immer denselben Wert haben soll

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

BEGIN TRANSACTION;

    SELECT @Saldo = Saldo FROM dbo.Konten WHERE KontoNr = 1;

    -- ... komplexe Berechnung ...

    SELECT @Saldo_Check = Saldo FROM dbo.Konten WHERE KontoNr = 1;

    -- Garantiert: @Saldo = @Saldo_Check — niemand hat geändert

    IF @Saldo <> @Saldo_Check RAISERROR('Concurrent modification detected.', 16, 1);

COMMIT;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

 

-- SERIALIZABLE: wenn die Ergebnismenge einer Abfrage stabil bleiben muss

-- z.B. Jahresabschluss: keine neuen Buchungen während der Verarbeitung

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

    SELECT SUM(Betrag) FROM dbo.Buchungen WHERE Jahr = 2025;

    -- Garantiert: keine neue Zeile mit Jahr = 2025 kann entstehen

    -- während diese Transaktion aktiv ist (Range Lock auf Jahr = 2025)

COMMIT;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

 

-- SNAPSHOT: konsistente Lesansicht ohne Sperren (benötigt RCSI oder SI — Kapitel 8)

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

BEGIN TRANSACTION;

    -- Liest den Zustand der Datenbank zum Transaktionsstartpunkt

    -- Keine Sperren auf gelesene Daten → kein Blocking durch Schreiber

    SELECT * FROM dm.Fact_Auftraege WHERE Jahr = 2025;

COMMIT;

 

 

5.3 Isolationsstufen-Diagnose für die laufende Datenbank

 

-- Welche Isolationsstufen werden aktuell genutzt?

SELECT

    s.session_id,

    s.login_name,

    s.program_name,

    CASE s.transaction_isolation_level

        WHEN 0 THEN 'UNSPECIFIED'

        WHEN 1 THEN 'READ UNCOMMITTED'

        WHEN 2 THEN 'READ COMMITTED'

        WHEN 3 THEN 'REPEATABLE READ'

        WHEN 4 THEN 'SERIALIZABLE'

        WHEN 5 THEN 'SNAPSHOT'

    END                             AS Isolationsstufe,

    s.open_transaction_count,

    s.last_request_start_time

FROM sys.dm_exec_sessions s

WHERE s.is_user_process = 1

  AND s.transaction_isolation_level = 1   -- READ UNCOMMITTED filtern

ORDER BY s.login_name;

-- Viele READ UNCOMMITTED Sessions → NOLOCK-Problem (Kapitel 4, Punkt 6)

 

 


 

 

06

Locking und Blocking

 

Sperren verstehen, Blocking erkennen und Wartezeiten reduzieren

 

6.1 Lock-Typen in SQL Server

 

SQL Server verwendet eine Lock-Hierarchie — von fein-granular (Row) bis grob-granular (Table):

 

Lock-Typ

Abkürzung

Wirkung

Kompatibel mit

Shared

S

Lesen — andere dürfen auch lesen

S, IS, U

Update

U

Lesen mit Absicht zu ändern

S, IS

Exclusive

X

Schreiben — exklusiv

Nichts

Intent Shared

IS

Zeigt an: Shared Lock auf tieferer Ebene

S, IS, IX, U

Intent Exclusive

IX

Zeigt an: Exclusive Lock auf tieferer Ebene

IS, IX

Schema

Sch

Schemaänderungen

Nichts

 

6.2 Aktuelles Blocking analysieren

 

-- Blocking-Überblick: wer blockiert wen?

SELECT

    blocking.session_id             AS Blocker_Session,

    blocking.login_name             AS Blocker_Login,

    blocking.host_name              AS Blocker_Host,

    blocked.session_id              AS Geblockt_Session,

    blocked.login_name              AS Geblockt_Login,

    DATEDIFF(SECOND,

        blocked.last_request_start_time,

        GETDATE())                  AS Geblockt_Seit_Sek,

    wt.wait_type                    AS Warte_Typ,

    wt.wait_duration_ms             AS Wartezeit_ms,

    SUBSTRING(bt.text, 1, 200)      AS Blocker_SQL,

    SUBSTRING(wt_text.text, 1, 200) AS Geblockt_SQL

FROM sys.dm_exec_sessions blocked

JOIN sys.dm_exec_requests r ON blocked.session_id = r.session_id

JOIN sys.dm_exec_sessions blocking ON r.blocking_session_id = blocking.session_id

JOIN sys.dm_os_waiting_tasks wt ON blocked.session_id = wt.session_id

CROSS APPLY sys.dm_exec_sql_text(blocking.most_recent_sql_handle) bt

CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) wt_text

WHERE r.blocking_session_id > 0

ORDER BY Geblockt_Seit_Sek DESC;

 

-- Historisches Blocking-Pattern: welche Tabellen und Objekte sind am häufigsten betroffen?

SELECT

    wt.wait_type,

    COUNT(*)                        AS Haeufigkeit,

    AVG(wt.wait_duration_ms)        AS Avg_Wartezeit_ms,

    MAX(wt.wait_duration_ms)        AS Max_Wartezeit_ms,

    SUM(wt.wait_duration_ms)        AS Gesamt_Wartezeit_ms

FROM sys.dm_os_wait_stats wt

WHERE wt.wait_type LIKE 'LCK_%'     -- nur Lock-Waits

  AND wt.waiting_tasks_count > 0

ORDER BY Gesamt_Wartezeit_ms DESC;

 

 

6.3 Lock Escalation kontrollieren

 

SQL Server eskaliert bei mehr als 5.000 Locks auf einer Tabelle automatisch auf einen Table Lock. Das blockiert dann alle anderen Transaktionen auf dieser Tabelle.

 

-- Lock Escalation für einzelne Tabellen deaktivieren (bei großen Batch-Operationen)

-- Vorsicht: kann zu erhöhtem Lock-Speicherverbrauch führen

ALTER TABLE dbo.Lagerbuchungen SET (LOCK_ESCALATION = DISABLE);

 

-- Alternativ: Lock Escalation auf Partition-Ebene begrenzen (bei partitionierten Tabellen)

ALTER TABLE dbo.Lagerbuchungen SET (LOCK_ESCALATION = AUTO);

 

-- Lock Escalation Events überwachen

SELECT

    OBJECT_NAME(object_id)          AS Tabelle,

    index_id,

    lock_escalation_desc            AS Eskalations_Einstellung

FROM sys.tables

WHERE lock_escalation_desc <> 'TABLE'   -- non-default Einstellungen

ORDER BY OBJECT_NAME(object_id);

 

 


 

 

07

Deadlocks

 

Erkennen, analysieren und dauerhaft verhindern

 

7.1 Was ist ein Deadlock?

 

Ein Deadlock entsteht, wenn zwei Transaktionen je eine Ressource halten, die die andere braucht — und beide aufeinander warten. SQL Server erkennt den Deadlock nach maximal fünf Sekunden und wählt das "günstigere" Opfer (die Transaktion mit dem geringsten Rollback-Aufwand) und rollt sie zurück.

 

Session A hält Sperre auf Tabelle Kunden,

         wartet auf Sperre auf Tabelle Auftraege.

 

Session B hält Sperre auf Tabelle Auftraege,

         wartet auf Sperre auf Tabelle Kunden.

 

→ Deadlock. SQL Server wählt Opfer → Rollback → Fehler 1205.

 

 

7.2 Deadlocks aufzeichnen und analysieren

 

-- Extended Events: Deadlock-Graph dauerhaft aufzeichnen

CREATE EVENT SESSION Deadlock_Capture ON SERVER

ADD EVENT sqlserver.xml_deadlock_report

ADD TARGET package0.ring_buffer

    (SET max_memory = 51200)

WITH (MAX_DISPATCH_LATENCY = 5 SECONDS);

 

ALTER EVENT SESSION Deadlock_Capture ON SERVER STATE = START;

 

-- Deadlock-Graphen aus dem Ring Buffer lesen

SELECT

    xdr.value('@timestamp', 'datetime2')            AS Zeitpunkt,

    xdr.query('.')                                  AS Deadlock_Graph_XML

FROM (

    SELECT CAST(target_data AS XML) AS target_data

    FROM sys.dm_xe_session_targets t

    JOIN sys.dm_xe_sessions s ON t.event_session_address = s.address

    WHERE s.name = 'Deadlock_Capture'

      AND t.target_name = 'ring_buffer'

) AS data

CROSS APPLY target_data.nodes('//RingBufferTarget/event') AS XEventData(xdr)

ORDER BY Zeitpunkt DESC;

 

-- Alternativ: Trace Flag 1222 — ausführlichere Deadlock-Informationen im Error Log

DBCC TRACEON (1222, -1);   -- global aktivieren

-- Im SQL Server Error Log: EXEC xp_readerrorlog 0, 1, 'deadlock';

 

 

7.3 Deadlocks systematisch verhindern

 

-- Strategie 1: konsistente Zugriffsreihenfolge in ALLEN Prozeduren

-- Wenn jede Transaktion immer zuerst Kunden, dann Auftraege sperrt — kein Deadlock möglich

 

-- FALSCH: Session A: Kunden → Auftraege; Session B: Auftraege → Kunden

-- RICHTIG: beide immer in derselben Reihenfolge

 

CREATE OR ALTER PROCEDURE dbo.SP_Transfer_Korrekt

    @VonKunde INT, @NachKunde INT, @Betrag DECIMAL(18,2)

AS

BEGIN

    SET NOCOUNT ON;

    -- Immer kleinere ID zuerst sperren → konsistente Reihenfolge

    DECLARE @ErsteID INT  = MIN(@VonKunde, @NachKunde);

    DECLARE @ZweiteID INT = MAX(@VonKunde, @NachKunde);

 

    BEGIN TRANSACTION;

    BEGIN TRY

        UPDATE dbo.Kunden SET Guthaben = Guthaben - @Betrag

        WHERE KundenNr = @ErsteID;   -- immer kleinere ID zuerst

        UPDATE dbo.Kunden SET Guthaben = Guthaben + @Betrag

        WHERE KundenNr = @ZweiteID;

        COMMIT;

    END TRY

    BEGIN CATCH

        IF XACT_STATE() <> 0 ROLLBACK;

        THROW;

    END CATCH

END

 

-- Strategie 2: Zeilenversion statt Sperren (RCSI — Kapitel 8)

-- Lesende Transaktionen sperren nichts mehr → Deadlock-Klasse "Leser vs. Schreiber" entfällt

 

-- Strategie 3: Retry-Logik in der Anwendung für Deadlock-Opfer (Fehler 1205)

-- Deadlocks sind nie vollständig eliminierbar — Anwendung muss damit umgehen können

 

-- Strategie 4: Sperr-Timeout als Sicherheitsnetz

SET LOCK_TIMEOUT 5000;   -- nach 5 Sekunden Wartezeit → Fehler statt unendliches Warten

-- Gibt Fehler 1222 zurück — Anwendung muss reagieren

 

 


 

 

08

Optimistic Concurrency — RCSI und Snapshot Isolation

 

Sperrkonflikte ohne Blocking lösen

 

8.1 Was ist RCSI und warum verändert es alles?

 

Read Committed Snapshot Isolation (RCSI) ist eine Datenbankeinstellung, die das Verhalten von READ COMMITTED grundlegend verändert: Lesende Transaktionen legen keine Shared Locks mehr — sie lesen die zuletzt committete Version einer Zeile aus dem tempdb-Versionsspeicher.

 

Vorher (ohne RCSI):

- Leser blockiert Schreiber, Schreiber blockiert Leser.

- Power BI Report wartet auf ERP-Buchungsvorgang.

- ETL-Job wartet auf Benutzerabfrage.

 

Nachher (mit RCSI):

- Leser und Schreiber blockieren sich nie gegenseitig.

- Power BI Report läuft immer — liest die zuletzt committete Version.

- ETL-Job und Benutzerabfragen koexistieren ohne Konflikt.

 

-- RCSI aktivieren (einmalige Operation, erfordert kurze Exklusivität)

-- Schritt 1: Andere Verbindungen trennen

-- Schritt 2: RCSI aktivieren

ALTER DATABASE ERP_Produktiv SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;

 

-- Schritt 3: Prüfen ob aktiviert

SELECT

    name,

    is_read_committed_snapshot_on,

    snapshot_isolation_state_desc

FROM sys.databases

WHERE name = 'ERP_Produktiv';

 

-- Kosten: tempdb wird für Zeilenversionen genutzt — tempdb muss auf SSD liegen!

-- Monitoring: Versionsspeicher-Größe im Auge behalten

SELECT

    reserved_mb    = SUM(version_store_reserved_page_count) * 8.0 / 1024,

    used_mb        = SUM(version_store_used_page_count) * 8.0 / 1024

FROM sys.dm_db_file_space_usage

WHERE database_id = 2;  -- tempdb

 

 

8.2 Snapshot Isolation vs. RCSI — der Unterschied

 

-- RCSI: READ COMMITTED verhält sich wie Snapshot — automatisch, keine Code-Änderung

-- Snapshot Isolation: explizite Transaktion mit konsistentem Startpunkt

 

-- Snapshot Isolation aktivieren (unabhängig von RCSI)

ALTER DATABASE ERP_Produktiv SET ALLOW_SNAPSHOT_ISOLATION ON;

 

-- Snapshot-Transaktion: liest immer den Zustand zum Transaktionsstartpunkt

-- Auch wenn andere Transaktionen zwischendurch committen

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

BEGIN TRANSACTION;

    -- Zeitpunkt T1: Bestand = 100

    SELECT Bestand FROM dbo.Lagerbestand WHERE ArtikelNr = 'A001';

    -- Andere Transaktion committed: Bestand = 80

 

    -- Snapshot liest immer noch T1-Zustand: Bestand = 100

    SELECT Bestand FROM dbo.Lagerbestand WHERE ArtikelNr = 'A001';

    -- Ergebnis: 100 (nicht 80!) — konsistente Sicht zum Transaktionsstartpunkt

COMMIT;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

 

-- Wann RCSI, wann Snapshot Isolation?

-- RCSI:     für alle normalen READ COMMITTED Leseanfragen — Power BI, ERP-Masken

-- Snapshot: für Transaktionen die eine konsistente Sicht über mehrere Statements brauchen

--           z.B. Berichtsextraktion, Jahresabschluss, lange Analyse-Transaktionen

 

 

8.3 Update Conflicts unter Snapshot Isolation

 

-- Snapshot Isolation erkennt Update-Konflikte: wenn zwei Transaktionen

-- dieselbe Zeile ändern wollen, gewinnt die erste — die zweite bekommt Fehler 3960

-- Dies ist optimistic concurrency: kein Warten, stattdessen Fehler + Retry

 

-- Retry-Logik in Stored Procedure für Snapshot Isolation

CREATE OR ALTER PROCEDURE dbo.SP_Lager_Update_Mit_Retry

    @ArtikelNr  NVARCHAR(20),

    @Menge      DECIMAL(18,4)

AS

BEGIN

    SET NOCOUNT ON;

    DECLARE @Versuche INT = 0;

    DECLARE @MaxVersuche INT = 3;

 

    WHILE @Versuche < @MaxVersuche

    BEGIN

        BEGIN TRY

            SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

            BEGIN TRANSACTION;

                UPDATE dbo.Lagerbestand

                SET Menge = Menge - @Menge

                WHERE ArtikelNr = @ArtikelNr AND Menge >= @Menge;

                IF @@ROWCOUNT = 0

                    RAISERROR('Kein ausreichender Bestand.', 16, 1);

            COMMIT TRANSACTION;

            SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

            RETURN 0;   -- Erfolg

 

        END TRY

        BEGIN CATCH

            IF XACT_STATE() <> 0 ROLLBACK;

            SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

 

            -- Fehler 3960: Snapshot-Update-Konflikt → Retry

            IF ERROR_NUMBER() = 3960 AND @Versuche < @MaxVersuche - 1

            BEGIN

                SET @Versuche += 1;

                WAITFOR DELAY '00:00:00.100';   -- 100ms warten vor Retry

            END

            ELSE

                THROW;   -- anderer Fehler oder max. Versuche → nach oben

        END CATCH

    END

END

 

 


 

 

09

Transaktionen in ERP und Anwendungsarchitektur

 

Lange Transaktionen, Connection Pooling und Retry-Logik

 

9.1 ERP-spezifische Transaktionsmuster

 

ERP-Systeme führen intern komplexe Transaktionen aus — oft über Dutzende von Tabellen und Sekunden bis Minuten Laufzeit. Das sind die Hauptquellen für Blocking in KMU-Umgebungen.

 

-- ERP-Transaktions-Diagnostik: welche ERP-Prozesse verursachen Blocking?

SELECT

    r.session_id,

    r.blocking_session_id,

    s.program_name                          AS ERP_Prozess,

    s.host_name,

    r.wait_type,

    r.wait_time / 1000.0                    AS Wartezeit_Sek,

    r.total_elapsed_time / 1000.0           AS Laufzeit_Sek,

    SUBSTRING(t.text, 1, 300)               AS Aktuelles_Statement,

    -- Transaktionsgröße schätzen

    r.writes * 8.0 / 1024                   AS Schreibmenge_MB,

    r.logical_reads / 1000.0               AS Lesemenge_K_Pages

FROM sys.dm_exec_requests r

JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id

CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t

WHERE r.blocking_session_id > 0

  AND s.is_user_process = 1

ORDER BY r.wait_time DESC;

 

-- Typische ERP-Blocking-Quellen und Gegenmaßnahmen:

-- 1. Lohnlauf (SAP PC00, Dynamics Payroll): Wartungsfenster einrichten

-- 2. Monatsabschluss: separaten Datenbankbenutzer ohne Concurrent-User-Zugriff

-- 3. MRP/Bedarfsplanung: Read Committed Snapshot aktivieren (Kapitel 8)

-- 4. Massenimporte (EDI, CSV): Batch-Größe begrenzen, außerhalb Kernzeiten

 

 

9.2 Connection Pooling und Transaktionslebensdauer

 

-- Transaktionen über Connection-Pooling-Grenzen hinweg diagnostizieren

-- Verbindungen mit ungewöhnlich langen Transaktionszeiten

SELECT

    s.session_id,

    s.login_name,

    s.program_name,

    s.open_transaction_count,

    DATEDIFF(SECOND, at.transaction_begin_time, GETDATE()) AS Trans_Dauer_Sek,

    at.transaction_type,

    at.transaction_state,

    -- Transaktion die länger als 5 Minuten offen ist: fast immer ein Bug

    CASE WHEN DATEDIFF(SECOND, at.transaction_begin_time, GETDATE()) > 300

         THEN 'KRITISCH: > 5 Min'

         WHEN DATEDIFF(SECOND, at.transaction_begin_time, GETDATE()) > 60

         THEN 'WARNUNG: > 1 Min'

         ELSE 'OK'

    END AS Bewertung

FROM sys.dm_exec_sessions s

JOIN sys.dm_tran_session_transactions st ON s.session_id = st.session_id

JOIN sys.dm_tran_active_transactions at ON st.transaction_id = at.transaction_id

WHERE s.is_user_process = 1

  AND at.transaction_state = 2   -- aktive Transaktionen

ORDER BY Trans_Dauer_Sek DESC;

 

 

9.3 Retry-Logik — unverzichtbar für robuste Anwendungen

 

Jede Anwendung, die SQL Server nutzt, muss mit transienten Fehlern umgehen können. Deadlocks (Fehler 1205), Snapshot-Konflikte (3960) und Timeouts (1222) sind keine Bugs — sie sind erwartete Ausnahmesituationen.

 

-- Retry-fähige Stored Procedure: Beispiel für kritische Buchungen

CREATE OR ALTER PROCEDURE dbo.SP_Buchung_Mit_Retry

    @BuchungsID     INT,

    @MaxVersuche    INT = 3

AS

BEGIN

    SET NOCOUNT ON;

    DECLARE @Versuch INT = 1;

    DECLARE @Erfolg  BIT = 0;

 

    WHILE @Versuch <= @MaxVersuche AND @Erfolg = 0

    BEGIN

        BEGIN TRY

            -- Eigentliche Buchungslogik

            EXEC dbo.SP_Buchung_Intern @BuchungsID;

            SET @Erfolg = 1;

 

        END TRY

        BEGIN CATCH

            IF XACT_STATE() <> 0 ROLLBACK;

 

            DECLARE @FehlerNr INT = ERROR_NUMBER();

 

            -- Retry-fähige Fehler

            IF @FehlerNr IN (

                1205,   -- Deadlock-Opfer

                1222,   -- Lock-Timeout

                3960,   -- Snapshot-Update-Konflikt

                40001,  -- Azure SQL Deadlock

                40613   -- Azure SQL Datenbank nicht verfügbar

            ) AND @Versuch < @MaxVersuche

            BEGIN

                -- Exponentielles Backoff: 100ms, 200ms, 400ms

                DECLARE @Wartezeit NVARCHAR(12) =

                    '00:00:00.' + RIGHT('000' + CAST(@Versuch * 100 AS VARCHAR), 3);

                WAITFOR DELAY @Wartezeit;

                SET @Versuch += 1;

            END

            ELSE

            BEGIN

                -- Nicht retry-fähig oder Max-Versuche erreicht → weiterwerfen

                INSERT INTO dbo.Fehlerprotokoll

                    (Prozedur, FehlerNr, Meldung, Versuch, Zeitpunkt)

                VALUES ('SP_Buchung_Mit_Retry', @FehlerNr,

                        ERROR_MESSAGE(), @Versuch, SYSUTCDATETIME());

                THROW;

            END

        END CATCH

    END

END

 

 


 

 

10

30-Tage-Stabilitätsplan

 

Von Blocking und Inkonsistenzen zur robusten Transaktionsarchitektur

 

 

VOR DEM START

Führen Sie die Diagnose-Abfragen aus den Kapiteln 3.1 (offene Transaktionen), 6.2 (aktuelles Blocking) und 4.8 (Log-Füllstand) als Baseline aus und dokumentieren Sie die Ergebnisse. Erstellen Sie ein vollständiges Backup. Kommunizieren Sie geplante Änderungen an Isolationsstufen und Datenbankeinstellungen an alle Anwendungsbetreiber — diese Änderungen können Anwendungsverhalten beeinflussen.

 

WOCHE 1: DIAGNOSE UND SOFORTMASSNAHMEN

 

■ TAG 1-2: BASELINE-MESSUNG

 

  Blocking-Diagnose aus Kapitel 6.2 ausführen und alle aktiven Blocking-Ketten dokumentieren

  Offene Transaktionen aus Kapitel 3.1 messen: gibt es Sessions > 5 Minuten offen?

  Isolationsstufen-Audit aus Kapitel 5.3: wie viele Sessions nutzen READ UNCOMMITTED?

  Transaction Log-Füllstand aus Kapitel 2.4 für alle produktiven Datenbanken messen

 

■ TAG 3-4: KRITISCHE SOFORTMASSNAHMEN

 

  Log-Backup-Jobs prüfen: laufen sie? Welches Recovery Model ist gesetzt?

  Fehlende Log-Backups sofort nachholen — Durability-Risiko beseitigen

  Alle NOLOCK-Hinweise in Stored Procedures und Views inventarisieren (grep/Suche)

  SET IMPLICIT_TRANSACTIONS auf allen Anwendungsverbindungen prüfen und auf OFF stellen

 

■ TAG 5-7: DEADLOCK-MONITORING AKTIVIEREN

 

  Extended Events Session "Deadlock_Capture" aus Kapitel 7.2 anlegen und starten

  Trace Flag 1222 global aktivieren für ausführlichere Deadlock-Informationen

  SQL Agent Job: täglicher Deadlock-Report aus Ring Buffer, Ergebnis per Mail

  Lock-Timeout als Sicherheitsnetz auf Anwendungsverbindungen setzen: SET LOCK_TIMEOUT 30000

 

WOCHE 2: RCSI UND ISOLATIONSSTUFEN

 

■ TAG 8-10: RCSI AKTIVIEREN

 

  tempdb-Lage prüfen: liegt tempdb auf SSD? Wenn nein, vor RCSI-Aktivierung sicherstellen

  Versionsspeicher-Monitoring einrichten: wöchentlicher Report auf tempdb-Auslastung

  RCSI auf einer Nicht-Produktions-Datenbank aktivieren und testen

  RCSI auf Produktivdatenbank aktivieren: ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ON

 

■ TAG 11-13: NOLOCK BEREINIGEN

 

  Alle WITH (NOLOCK)-Hinweise in Berichten und Abfragen auf Finanzdaten entfernen

  Testen: laufen die Abfragen jetzt schnell genug ohne NOLOCK? (Mit RCSI: ja)

  Remaining NOLOCK: nur wo explizit "Ungefähr-Zahlen" semantisch korrekt sind

  Power BI Refresh-Zeiten vorher/nachher messen: RCSI sollte keine Verlangsamung bringen

 

■ TAG 14: SNAPSHOT ISOLATION FÜR BERICHTE AKTIVIEREN

 

  ALLOW_SNAPSHOT_ISOLATION auf produktiven Datenbanken aktivieren

  ETL-Jobs und Reporting-Queries auf SNAPSHOT Isolation umstellen

  Sicherstellen: ETL liest nicht mehr halbfertige Datensätze während ERP-Buchung

  Versionsspeicher-Wachstum einen Tag beobachten — Baseline für späteres Monitoring

 

WOCHE 3: TRANSAKTIONS-QUALITÄT ERHÖHEN

 

■ TAG 15-17: STORED PROCEDURES AUFRÜSTEN

 

  Alle produktiven Stored Procedures ohne TRY/CATCH identifizieren

  Robustes Template aus Kapitel 3.2 auf die kritischsten 10 Prozeduren anwenden

  XACT_STATE()-basierter ROLLBACK in allen Prozeduren mit Transaktionen sicherstellen

  SET NOCOUNT ON in alle Prozeduren eintragen die es noch fehlt

 

■ TAG 18-20: LANGE TRANSAKTIONEN KÜRZEN

 

  Die drei längsten ERP-Transaktionen aus Kapitel 9.1 identifizieren

  Für jede: kann Vorverarbeitung außerhalb der Transaktion verschoben werden?

  Batch-Delete-Operationen auf Loop-Muster aus Kapitel 4.9 umstellen

  Lohnlauf, Monatsabschluss, MRP: Wartungsfenster definieren und kommunizieren

 

■ TAG 21: RETRY-LOGIK EINFÜHREN

 

  Deadlock-Fehler (1205) in der Anwendung identifizieren: wie wird bisher reagiert?

  Retry-Wrapper aus Kapitel 9.3 für alle kritischen Buchungs-Stored-Procedures einführen

  Anwendungsentwickler briefen: transiente Fehler erwarten, nicht als Bug behandeln

  Test: Deadlock künstlich erzeugen, Retry-Logik verifizieren

 

WOCHE 4: MONITORING UND DAUERBETRIEB

 

■ TAG 22-25: AUTOMATISCHES MONITORING AUFBAUEN

 

  Blocking-Report als SQL Agent Job: täglich 08:00 Uhr, Alert wenn Blocking > 60 Sek

  Deadlock-Report: wöchentlich, Top-10 Deadlock-Paare, an DBA und Entwicklung

  Transaction Log-Monitoring: Alert wenn Log > 70 % voll

  Lange Transaktionen: Alert wenn Session mit offener Transaktion > 10 Minuten

 

■ TAG 26-28: LEISTUNGSMESSUNG

 

  Blocking-Wartezeiten vergleichen: Vorher/Nachher nach RCSI-Aktivierung

  Power BI Refresh-Zeiten vergleichen: Vorher/Nachher

  Deadlock-Häufigkeit vergleichen: Wochen 1–2 vs. Wochen 3–4

  ERP-Antwortzeiten während Batch-Jobs messen: Verbesserung durch Snapshot Isolation?

 

■ TAG 29-30: DOKUMENTATION UND GOVERNANCE

 

  ACID-Ist-Zustand dokumentieren: welche Isolationsstufen wo, welche Prozeduren mit TRY/CATCH

  Transaktions-Coding-Standard für Stored Procedures schreiben: Template, XACT_STATE, THROW

  Onboarding-Leitfaden: neue Entwickler lernen das Template als ersten Schritt

  Nächste Schritte planen: Optimized Locking (SQL Server 2025), Always Encrypted für sensible Felder

  Ergebnis feiern — eine robuste Transaktionsarchitektur ist unsichtbar wenn sie funktioniert! ■

 

 

ERGEBNIS NACH 30 TAGEN

Ihr Ergebnis nach 30 Tagen: RCSI aktiviert und Power BI greift nie mehr blockierend auf ERP-Daten zu, alle kritischen Stored Procedures haben TRY/CATCH und XACT_STATE()-basierten ROLLBACK, Deadlocks werden aufgezeichnet und analysiert, ein automatisches Monitoring meldet Blocking und Log-Probleme bevor Benutzer es merken — und das Transaction Log läuft nicht mehr voll.

 


 

 

Ausführlicher Haftungsausschluss und Lizenzbestimmungen

 

1. Allgemeiner Haftungsausschluss

 

Die in diesem Dokument enthaltenen Informationen, Skripte und Empfehlungen wurden nach bestem Wissen und Gewissen auf der Grundlage langjähriger praktischer Erfahrung erstellt. Änderungen an Transaktionskonfigurationen, Isolationsstufen und Datenbankeinstellungen haben systemweite Auswirkungen. Der Autor übernimmt keinerlei Haftung für Datenverlust, Systemausfälle, Inkonsistenzen oder sonstige unmittelbare oder mittelbare Schäden.

 

Änderungen wie RCSI-Aktivierung, Isolationsstufen-Wechsel und Trace Flag-Konfigurationen müssen zwingend zuerst in einer Testumgebung mit produktionsnahen Daten und Lastprofilen validiert werden.

 

2. Performance-Angaben

 

Genannte Performance-Gewinne durch RCSI, Snapshot Isolation und optimierte Transaktionsmuster sind Erfahrungswerte. Tatsächliche Ergebnisse hängen von Hardware, SQL Server Version, Datenvolumen, tempdb-Konfiguration und Abfragemustern ab. RCSI kann bei extrem hoher Schreiblast durch den Versionsspeicher zu tempdb-Belastung führen — vor Aktivierung messen.

 

3. Versionsabhängigkeit und Aktualität

 

Die Inhalte beziehen sich auf SQL Server 2022 (16.x) und SQL Server 2025 (17.x) sowie Azure SQL Database, Stand März 2026. Optimized Locking (SQL Server 2025) und weitere Features können das in diesem Kit beschriebene Locking-Verhalten weiterentwickeln.

 

4. Urheberrecht und Nutzungsrechte

 

© 2026 Sascha Hess, xenosystems.de. Alle Rechte vorbehalten. Dieses Dokument ist für den persönlichen oder betriebsinternen Gebrauch des Käufers lizenziert.

 

5. Markenrechte

 

SQL Server, Azure SQL Database und weitere Microsoft-Produktnamen sind eingetragene Marken der Microsoft Corporation.

 

6. Anwendbares Recht und Gerichtsstand

 

Es gilt ausschließlich deutsches Recht. Gerichtsstand für alle Streitigkeiten ist, soweit gesetzlich zulässig, Weimar, Thüringen, Deutschland.

 


 

 

Über den Autor

 

Sascha Hess ist Diplom-Biologe und IT-Professional mit über 20 Jahren Erfahrung in der Administration von ERP-, BI- und Datenbanksystemen. Er hat mehr als 300 Oracle- und SQL-Server-Instanzen administriert und betreut — von mittelständischen KMU bis zu Universitäten und Energieversorgern.

 

Sein Ansatz verbindet naturwissenschaftliche Präzision mit hochgradiger IT-Spezialisierung. Schwerpunkte: SQL Server Performance-Tuning, Transaktionsarchitektur, ERP-Einführungen, Business Intelligence (Power BI, DeltaMaster), Prozessdigitalisierung und IT-Interim-Management.

 

Web: www.xenosystems.de | E-Mail: info@xenosystems.de | Standort: Weimar, Thüringen / Remote

 

Service

Beschreibung

SQL Server DB Health Check

Professioneller Audit Ihres SQL Servers — Performance, Transaktionsmuster, Blocking-Analyse, Isolationsstufen und Backup-Strategie. Scope: 3-5 Tage.

Transaktions-Architektur-Review

Analyse bestehender Stored Procedures und Transaktionsmuster — Blocking-Ursachen, Deadlock-Analyse, RCSI-Readiness, Refactoring-Plan.

SQL Server Performance-Tuning

Gezielte Optimierung von Abfragen, Indizes, Isolationsstufen und Transaktionsarchitektur in produktiven Umgebungen.

ERP-Einführungsberatung

Begleitung von ERP-Projekten (MACH, Dynamics NAV, APplus) — Datenbankdesign, Transaktionskonzept, Performance-Architektur, Go-Live-Support.

Interim IT-Management

Übernahme der IT-Steuerung auf Zeit — Budgetplanung, Dienstleister-Management, strategische IT-Ausrichtung.

 

Vollständiges Dokument

fundament kit transaktionen acid 2026

Strategische Wissens-Roadmap · Checklisten · Praxisbeispiele

49,90 €

Sichere Zahlung über PayPal · Sofort-Download nach Zahlungseingang