Skip to content
IRC-Coding IRC-Coding
Datenbanken Transaktionen ACID Isolation Levels Deadlocks Optimistic Concurrency SQL Datenbank-Management

Datenbank Transaktionen: ACID, Isolation Levels & Deadlocks

Datenbank Transaktionen mit ACID-Eigenschaften, Isolation Levels, Deadlocks und optimistischer Konkurrenz. SQL Beispiele für Transaktionsmanagement.

S

schutzgeist

2 min read

Datenbank Transaktionen: ACID, Isolation Levels & Deadlocks

Datenbank-Transaktionen sind fundamental für die Gewährleistung von Datenkonsistenz und -integrität in modernen Anwendungen. Sie ermöglichen sichere, zuverlässige Operationen auch bei gleichzeitigem Zugriff mehrerer Benutzer.

Was sind Transaktionen?

Definition und Grundlagen

Eine Transaktion ist eine logische Einheit von Arbeit, die aus einer oder mehreren Datenbankoperationen besteht. Transaktionen müssen als atomare Einheit behandelt werden - entweder werden alle Operationen erfolgreich ausgeführt oder keine.

Transaktions-Eigenschaften

-- Transaktion als logische Einheit
BEGIN TRANSACTION;

-- Operation 1: Konto abbuchen
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;

-- Operation 2: Konto gutschreiben  
UPDATE Konten SET saldo = saldo + 100 WHERE konto_id = 2;

-- Entweder beide Operationen erfolgreich oder keine
COMMIT;
-- oder bei Fehler: ROLLBACK;

ACID-Eigenschaften

Atomicity (Atomarität)

Atomicity stellt sicher, dass eine Transaktion entweder vollständig oder gar nicht ausgeführt wird.

-- Beispiel für Atomarität
CREATE TABLE Transaktionen (
    trans_id INT PRIMARY KEY AUTO_INCREMENT,
    von_konto INT,
    zu_konto INT,
    betrag DECIMAL(10,2),
    status VARCHAR(20),
    zeitpunkt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Atomare Überweisung
DELIMITER //
CREATE PROCEDURE ueberweisung(
    IN von_konto_id INT,
    IN zu_konto_id INT,
    IN betrag DECIMAL(10,2)
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;
    
    START TRANSACTION;
    
    -- Prüfen ob Saldo ausreichend
    DECLARE aktuelles_saldo DECIMAL(10,2);
    SELECT saldo INTO aktuelles_saldo FROM Konten WHERE konto_id = von_konto_id;
    
    IF aktuelles_saldo < betrag THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Unzureichendes Saldo';
    END IF;
    
    -- Geld abbuchen
    UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
    
    -- Geld gutschreiben
    UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
    
    -- Transaktion protokollieren
    INSERT INTO Transaktionen (von_konto, zu_konto, betrag, status)
    VALUES (von_konto_id, zu_konto_id, betrag, 'ERFOLGREICH');
    
    COMMIT;
END //
DELIMITER ;

-- Aufruf
CALL ueberweisung(1, 2, 100.00);

Consistency (Konsistenz)

Consistency stellt sicher, dass die Datenbank nach einer Transaktion in einem konsistenten Zustand verbleibt.

-- Beispiel für Konsistenz-Regeln
CREATE TABLE Konten (
    konto_id INT PRIMARY KEY,
    inhaber VARCHAR(100),
    saldo DECIMAL(10,2) NOT NULL,
    CHECK (saldo >= 0)  -- Saldo darf nicht negativ sein
);

CREATE TABLE Ueberweisungsregeln (
    regel_id INT PRIMARY KEY,
    max_betrag_pro_tag DECIMAL(10,2),
    max_anzahl_pro_tag INT
);

-- Konsistenz-sichernde Transaktion
DELIMITER //
CREATE PROCEDURE konsistente_ueberweisung(
    IN von_konto_id INT,
    IN zu_konto_id INT,
    IN betrag DECIMAL(10,2)
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;
    
    START TRANSACTION;
    
    -- Business-Regel: Maximalbetrag prüfen
    DECLARE max_betrag DECIMAL(10,2);
    SELECT max_betrag_pro_tag INTO max_betrag 
    FROM Ueberweisungsregeln WHERE regel_id = 1;
    
    IF betrag > max_betrag THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Betrag exceeds maximum';
    END IF;
    
    -- Saldo-Prüfung (durch CHECK constraint erzwungen)
    DECLARE aktuelles_saldo DECIMAL(10,2);
    SELECT saldo INTO aktuelles_saldo FROM Konten WHERE konto_id = von_konto_id;
    
    IF aktuelles_saldo < betrag THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient funds';
    END IF;
    
    -- Operationen ausführen
    UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
    UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
    
    COMMIT;
END //
DELIMITER ;

Isolation (Isolation)

Isolation stellt sicher, dass gleichzeitig ausgeführte Transaktionen sich nicht gegenseitig beeinflussen.

-- Beispiel für Isolation
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;

-- Session 2 (parallel):
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;

-- Session 1 liest Daten
SELECT * FROM Konten WHERE konto_id = 1;

-- Session 2 modifiziert Daten
UPDATE Konten SET saldo = 1500 WHERE konto_id = 1;
COMMIT;

-- Session 1 liest erneut (je nach Isolation Level)
SELECT * FROM Konten WHERE konto_id = 1;

Durability (Dauerhaftigkeit)

Durability stellt sicher, dass die Änderungen einer Transaktion permanent gespeichert werden.

-- Beispiel für Durability
-- Nach COMMIT sind Änderungen permanent
START TRANSACTION;
UPDATE Konten SET saldo = 2000 WHERE konto_id = 1;
COMMIT;  -- Änderungen sind jetzt permanent

-- Auch bei Systemabsturz sind Änderungen erhalten
-- (durch Write-Ahead Logging und andere Mechanismen)

Isolation Levels

READ UNCOMMITTED

Niedrigster Isolation Level - erlaubt “Dirty Reads”.

-- READ UNCOMMITTED Beispiel
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE Konten SET saldo = 500 WHERE konto_id = 1;
-- Noch nicht committed!

-- Session 2:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM Konten WHERE konto_id = 1;
-- Liest den uncommitted Wert (500) - Dirty Read!

-- Session 1:
ROLLBACK;  -- Änderung wird zurückgerollt

-- Session 2 hat ungültige Daten gelesen

READ COMMITTED

Verhindert Dirty Reads, erlaubt aber Non-Repeatable Reads.

-- READ COMMITTED Beispiel
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT saldo FROM Konten WHERE konto_id = 1;  -- Liest 1000

-- Session 2:
START TRANSACTION;
UPDATE Konten SET saldo = 1500 WHERE konto_id = 1;
COMMIT;

-- Session 1 liest erneut:
SELECT saldo FROM Konten WHERE konto_id = 1;  -- Liest jetzt 1500
-- Non-Repeatable Read!

REPEATABLE READ

Verhindert Dirty Reads und Non-Repeatable Reads, erlaubt aber Phantom Reads.

-- REPEATABLE READ Beispiel
-- Session 1:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM Konten WHERE inhaber LIKE 'A%';  -- Liest 3 Konten

-- Session 2:
START TRANSACTION;
INSERT INTO Konten VALUES (4, 'Anna Schmidt', 2000);
COMMIT;

-- Session 1 liest erneut:
SELECT * FROM Konten WHERE inhaber LIKE 'A%';  -- Immer noch 3 Konten
-- Neue Zeile wird nicht gesehen (kein Phantom Read in MySQL)

SERIALIZABLE

Höchster Isolation Level - verhindert alle Anomalien.

-- SERIALIZABLE Beispiel
-- Session 1:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT AVG(saldo) FROM Konten;  -- Berechnet Durchschnitt

-- Session 2:
START TRANSACTION;
INSERT INTO Konten VALUES (5, 'Bernd Mueller', 3000);
-- Wartet bis Session 1 fertig ist!

-- Session 1:
COMMIT;

-- Session 2 kann jetzt fortfahren
COMMIT;

Konkurrenzkontrolle

Pessimistic Concurrency Control

Sperrt Ressourcen proaktiv, um Konflikte zu vermeiden.

-- Pessimistic Locking Beispiel
-- Explicit Locks
START TRANSACTION;

-- Zeile sperren
SELECT * FROM Konten WHERE konto_id = 1 FOR UPDATE;

-- Andere Transaktionen müssen warten
-- Session 2:
SELECT * FROM Konten WHERE konto_id = 1 FOR UPDATE;
-- Wartet bis Session 1 committed/rolled back

-- Operationen durchführen
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;

COMMIT;  -- Lock wird freigegeben

Optimistic Concurrency Control

Geht davon aus, dass Konflikte selten sind und löst sie bei Bedarf.

-- Optimistic Concurrency mit Version Column
CREATE TABLE Produkte (
    produkt_id INT PRIMARY KEY,
    name VARCHAR(100),
    preis DECIMAL(10,2),
    bestand INT,
    version INT DEFAULT 0
);

-- Update mit Versionsprüfung
DELIMITER //
CREATE PROCEDURE update_produkt_optimistic(
    IN produkt_id INT,
    IN neuer_preis DECIMAL(10,2),
    IN erwartete_version INT
)
BEGIN
    DECLARE affected_rows INT;
    
    START TRANSACTION;
    
    UPDATE Produkte 
    SET preis = neuer_preis, version = version + 1
    WHERE produkt_id = produkt_id AND version = erwartete_version;
    
    SET affected_rows = ROW_COUNT();
    
    IF affected_rows = 0 THEN
        ROLLBACK;
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Concurrency conflict - product was modified';
    ELSE
        COMMIT;
    END IF;
END //
DELIMITER ;

-- Anwendung
-- Zuerst lesen
SELECT produkt_id, name, preis, version FROM Produkte WHERE produkt_id = 1;

-- Update mit erwarteter Version
CALL update_produkt_optimistic(1, 29.99, 5);

Deadlocks

Deadlock-Erkennung und -Vermeidung

Deadlocks entstehen, wenn Transaktionen aufeinander warten.

-- Deadlock Beispiel
-- Session 1:
START TRANSACTION;
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
-- Wartet auf konto_id = 2
UPDATE Konten SET saldo = saldo + 100 WHERE konto_id = 2;

-- Session 2 (parallel):
START TRANSACTION;
UPDATE Konten SET saldo = saldo - 50 WHERE konto_id = 2;
-- Wartet auf konto_id = 1
UPDATE Konten SET saldo = saldo + 50 WHERE konto_id = 1;

-- DEADLOCK! Beide warten aufeinander

Deadlock-Vermeidungsstrategien

-- 1. Konsistente Reihenfolge der Locks
DELIMITER //
CREATE PROCEDURE sichere_ueberweisung(
    IN von_konto_id INT,
    IN zu_konto_id INT,
    IN betrag DECIMAL(10,2)
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        RESIGNAL;
    END;
    
    -- Immer in gleicher Reihenfolge locken
    IF von_konto_id < zu_konto_id THEN
        SET @lock1 = von_konto_id;
        SET @lock2 = zu_konto_id;
    ELSE
        SET @lock1 = zu_konto_id;
        SET @lock2 = von_konto_id;
    END IF;
    
    START TRANSACTION;
    
    -- Locks in konsistenter Reihenfolge
    SELECT * FROM Konten WHERE konto_id = @lock1 FOR UPDATE;
    SELECT * FROM Konten WHERE konto_id = @lock2 FOR UPDATE;
    
    -- Operationen durchführen
    IF von_konto_id < zu_konto_id THEN
        UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
        UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
    ELSE
        UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
        UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
    END IF;
    
    COMMIT;
END //
DELIMITER ;

-- 2. Timeout-basierte Wiederholung
DELIMITER //
CREATE PROCEDURE ueberweisung_mit_retry(
    IN von_konto_id INT,
    IN zu_konto_id INT,
    IN betrag DECIMAL(10,2)
)
BEGIN
    DECLARE retry_count INT DEFAULT 0;
    DECLARE max_retries INT DEFAULT 3;
    DECLARE deadlock_detected BOOLEAN DEFAULT FALSE;
    
    retry_loop: WHILE retry_count < max_retries DO
        BEGIN
            DECLARE EXIT HANDLER FOR 1213  -- Deadlock error code
            BEGIN
                SET deadlock_detected = TRUE;
                SET retry_count = retry_count + 1;
                IF retry_count < max_retries THEN
                    -- Kurze Wartezeit vor Retry
                    DO SLEEP(0.1 * retry_count);
                END IF;
            END;
            
            -- Transaktion durchführen
            CALL sichere_ueberweisung(von_konto_id, zu_konto_id, betrag);
            
            -- Erfolgreich - Schleife verlassen
            LEAVE retry_loop;
        END;
        
        IF deadlock_detected AND retry_count >= max_retries THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Max retries exceeded';
        END IF;
        
        SET deadlock_detected = FALSE;
    END WHILE;
END //
DELIMITER ;

Deadlock-Monitoring

-- Deadlock-Informationen abrufen (MySQL)
SHOW ENGINE INNODB STATUS;

-- Deadlock-Transaktionen überwachen
SELECT 
    r.trx_id waiting_trx_id,
    r.trx_mysql_thread_id waiting_thread,
    r.trx_query waiting_query,
    b.trx_id blocking_trx_id,
    b.trx_mysql_thread_id blocking_thread,
    b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;

-- Lock-Status anzeigen
SELECT 
    object_name,
    lock_type,
    lock_mode,
    lock_status,
    engine_transaction_id
FROM performance_schema.data_locks
WHERE object_name = 'Konten';

Transaktions-Management in verschiedenen Datenbanken

MySQL/MariaDB

-- MySQL-spezifische Features
-- Autocommit steuern
SET autocommit = 0;  -- Manuelle Transaktionssteuerung
SET autocommit = 1;  -- Automatisches Commit (default)

-- Savepoints für partielle Rollbacks
START TRANSACTION;
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
SAVEPOINT sp1;

UPDATE Konten SET saldo = saldo - 50 WHERE konto_id = 2;
SAVEPOINT sp2;

-- Zum Savepoint zurückrollen
ROLLBACK TO sp1;
COMMIT;  -- Nur erste Änderung bleibt bestehen

-- XA Transaktionen für verteilte Systeme
XA START 'xid1';
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
XA COMMIT 'xid1';

PostgreSQL

-- PostgreSQL-spezifische Features
-- Transaction Isolation Levels
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- Advisory Locks (application-level locking)
SELECT pg_advisory_lock(12345);  -- Lock erwerben
SELECT pg_advisory_unlock(12345);  -- Lock freigeben

-- Transaction-Snapshots
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM Konten WHERE konto_id = 1;

-- In anderer Session:
UPDATE Konten SET saldo = 2000 WHERE konto_id = 1;
COMMIT;

-- Original Session sieht immer noch alten Wert
SELECT * FROM Konten WHERE konto_id = 1;

Oracle

-- Oracle-spezifische Features
-- Read-Only Transaktionen
SET TRANSACTION READ ONLY;
SELECT * FROM Konten;  -- Garantiert konsistente Sicht

-- Autonomous Transactions
DELIMITER //
CREATE PROCEDURE log_transaktion(
    IN transaktion_id INT,
    IN beschreibung VARCHAR(200)
)
AS
BEGIN
    -- Autonomous Transaction
    PRAGMA AUTONOMOUS_TRANSACTION;
    
    INSERT INTO Transaktionslog (trans_id, beschreibung, zeitpunkt)
    VALUES (transaktion_id, beschreibung, SYSTIMESTAMP);
    
    COMMIT;  -- Commit nur für autonomous transaction
END;
//

-- Savepoints
SAVEPOINT sp1;
-- Operationen
ROLLBACK TO sp1;

Best Practices für Transaktions-Management

1. Transaktionen kurz halten

-- Schlecht: Lange Transaktion
START TRANSACTION;
SELECT * FROM grosse_tabelle;  -- Lange Abfrage
-- ... viele andere Operationen ...
UPDATE kleine_tabelle SET wert = 1;
COMMIT;

-- Gut: Kurze, fokussierte Transaktion
SELECT * FROM grosse_tabelle;  -- Außerhalb der Transaktion
START TRANSACTION;
UPDATE kleine_tabelle SET wert = 1;
COMMIT;

2. Richtigen Isolation Level wählen

-- Für die meisten Anwendungsfälle
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- Für analytische Abfragen
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- Für kritische Finanzoperationen
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

3. Fehlerbehandlung implementieren

-- Robuste Fehlerbehandlung
DELIMITER //
CREATE PROCEDURE robuste_transaktion()
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE,
                                   @errno = MYSQL_ERRNO,
                                   @text = MESSAGE_TEXT;
        
        ROLLBACK;
        
        -- Logging
        INSERT INTO error_log (error_time, error_code, error_message)
        VALUES (NOW(), @errno, @text);
        
        -- Fehler weitergeben
        RESIGNAL;
    END;
    
    START TRANSACTION;
    
    -- Transaktionslogik
    INSERT INTO tabelle1 (wert) VALUES (1);
    UPDATE tabelle2 SET wert = 2 WHERE id = 1;
    
    COMMIT;
END //
DELIMITER ;

4. Connection Pooling verwenden

// Java Beispiel mit Connection Pool
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class TransactionService {
    private DataSource dataSource;
    
    public void executeInTransaction(TransactionCallback callback) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            conn.setAutoCommit(false);
            
            callback.execute(conn);
            
            conn.commit();
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            throw new RuntimeException("Transaction failed", e);
        } finally {
            if (conn != null) {
                try {
                    conn.setAutoCommit(true);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    @FunctionalInterface
    public interface TransactionCallback {
        void execute(Connection conn) throws SQLException;
    }
}

Prüfungsrelevante Konzepte

Wichtige ACID-Eigenschaften

EigenschaftBeschreibungImplementierung
AtomicityAlles oder nichtsRollback, Write-Ahead Logging
ConsistencyKonsistenter ZustandConstraints, Triggers
IsolationKeine InterferenzenLocks, Isolation Levels
DurabilityPermanente SpeicherungRedo Logs, Checkpoints

Isolation Levels im Vergleich

LevelDirty ReadsNon-Repeatable ReadsPhantom ReadsPerformance
READ UNCOMMITTEDHöchste
READ COMMITTEDHoch
REPEATABLE READ✅ (MySQL: ❌)Mittel
SERIALIZABLENiedrigste

Typische Prüfungsaufgaben

  1. Erklären Sie ACID-Eigenschaften
  2. Vergleichen Sie Isolation Levels
  3. Implementieren Sie Deadlock-Vermeidung
  4. Wählen Sie richtige Transaktionsstrategien
  5. Analysieren Sie Konkurrenzprobleme

Zusammenfassung

Transaktions-Management ist fundamental für zuverlässige Datenbankanwendungen:

  • ACID-Eigenschaften garantieren Datenintegrität
  • Isolation Levels steuern Konkurrenzverhalten
  • Deadlock-Vermeidung sichert Systemstabilität
  • Optimistic vs Pessimistic Konkurrenzkontrolle
  • Best Practices optimieren Performance und Zuverlässigkeit

Gutes Transaktionsdesign erfordert Verständnis der Anwendungsanforderungen und der zugrundeliegenden Datenbank-Mechanismen.

Zurück zum Blog
Share:

Ähnliche Beiträge