CodeGym /Kurse /SQL SELF /Isolation Level REPEATABLE READ: Verhindern von Non-Repea...

Isolation Level REPEATABLE READ: Verhindern von Non-Repeatable Read

SQL SELF
Level 40 , Lektion 1
Verfügbar

Stell dir vor, du zockst ein Online-Game und währenddessen hackt sich irgendein Cheater in den Code und bufft seinen Charakter, oder du liest ein Buch in der Bibliothek und jemand ändert währenddessen die Seiten, fügt neue Kapitel ein oder tauscht das Buch komplett aus. Ziemlich nervig, oder? Genau vor solchen "Überraschungen" schützt dich das Isolation Level REPEATABLE READ.

REPEATABLE READ gibt dir die Garantie, dass die Daten, die du innerhalb einer Transaktion siehst, bis zum Ende dieser Transaktion unverändert bleiben. Selbst wenn eine andere Transaktion versucht, diese Daten zu ändern, bist du in deiner Transaktion vor solchen Änderungen sicher.

Wichtige Features:

  • Verhindert Dirty Read (Lesen von Daten, die noch nicht bestätigt wurden).
  • Und am wichtigsten: Verhindert Non-Repeatable Read. Das heißt, wenn du einen Datensatz am Anfang der Transaktion liest, bekommst du beim erneuten Lesen genau die gleichen Daten, selbst wenn ein anderer User sie geändert hat.

Allerdings schützt REPEATABLE READ nicht vor Phantom Read. Wenn eine andere Transaktion neue Zeilen hinzufügt, können sie bei deiner erneuten Abfrage auftauchen. Um auch diese Anomalie zu verhindern, brauchst du das Level SERIALIZABLE – aber dazu später mehr.

Wie setzt man das Isolation Level REPEATABLE READ

Bevor wir zu Beispielen kommen, schauen wir uns an, wie man dieses Isolation Level in PostgreSQL aktiviert. Es gibt zwei Hauptwege:

  1. Isolation Level für eine bestimmte Transaktion setzen:

    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    BEGIN;
    -- Deine Abfragen
    COMMIT;
    
  2. Isolation Level für die aktuelle Session setzen:

    SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    

Im zweiten Fall nutzen alle Transaktionen in der aktuellen Session REPEATABLE READ.

Beispiel: Verhindern von Non-Repeatable Read

Angenommen, wir haben eine Tabelle accounts mit folgender Struktur:

CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    customer_name TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'wartend'
);

INSERT INTO orders (customer_name, status)
VALUES ('Alice', 'wartend'), ('Bob', 'wartend');

Starten wir mit einem einfachen Szenario, in dem eine Transaktion Daten ändert und eine andere liest.

Szenario ohne REPEATABLE READ (Level READ COMMITTED)

Transaktion 1 startet:

BEGIN;
SELECT balance FROM accounts WHERE account_id = 1;
-- Ergebnis: 100

Währenddessen ändert Transaktion 2 die Daten:

BEGIN;
UPDATE accounts SET balance = 150 WHERE account_id = 1;
COMMIT;

Transaktion 1 macht weiter:

SELECT balance FROM accounts WHERE account_id = 1;
-- Ergebnis: 150 (Daten haben sich geändert!)
COMMIT;

Wie du siehst, können sich bei READ COMMITTED die Daten zwischen zwei Lesevorgängen innerhalb einer Transaktion ändern. Das ist genau das Non-Repeatable Read-Problem.

Szenario mit REPEATABLE READ

Jetzt probieren wir das gleiche Beispiel, aber mit dem Isolation Level REPEATABLE READ.

Transaktion 1:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM accounts WHERE account_id = 1;
-- Ergebnis: 100

Transaktion 2:

BEGIN;
UPDATE accounts SET balance = 150 WHERE account_id = 1;
COMMIT;

Transaktion 1 macht weiter:

SELECT balance FROM accounts WHERE account_id = 1;
-- Immer noch: 100 (Daten bleiben gleich!)
COMMIT;

Egal, was die andere Transaktion ändert, Transaktion 1 sieht die Daten so, wie sie beim Start waren. So wird Non-Repeatable Read verhindert.

Wie funktioniert REPEATABLE READ

PostgreSQL nutzt das MVCC-Prinzip (Multi-Version Concurrency Control), um das Isolation Level REPEATABLE READ umzusetzen. Das Grundprinzip von MVCC ist, dass jede Transaktion einen stabilen "Snapshot" der Datenbank bekommt, der sich bis zum Ende der Transaktion nicht ändert. Das wird durch das Erstellen und Verwalten mehrerer Versionen von Zeilen erreicht.

Wenn eine Transaktion startet, sieht sie die Daten so, wie sie zu diesem Zeitpunkt waren. Wenn eine andere Transaktion Änderungen macht, erstellt PostgreSQL eine neue Version der Zeile, aber die alte Version bleibt für alle laufenden Transaktionen erhalten, die sie noch brauchen.

Deshalb laufen Transaktionen manchmal langsam und brauchen viel Speicher. Und genau deshalb nutzt kaum jemand das stärkste Isolation Level: Es ist am sichersten, aber bremst die Arbeit mit der Datenbank am meisten aus.

Einschränkungen von REPEATABLE READ: Phantom Read

Wie schon erwähnt, schützt REPEATABLE READ nicht vor Phantom Read. Was das bedeutet, schauen wir uns mit einem Beispiel für Abfragen mit Datenbereichen an.

Nehmen wir an, wir haben eine Tabelle orders:

CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    amount NUMERIC NOT NULL
);

INSERT INTO orders (amount)
VALUES (50), (100), (150);

Transaktion 1:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT COUNT(*) FROM orders WHERE amount > 50;
-- Ergebnis: 2

Transaktion 2:

BEGIN;
INSERT INTO orders (amount) VALUES (200);
COMMIT;

Transaktion 1 macht weiter:

SELECT COUNT(*) FROM orders WHERE amount > 50;
-- Ergebnis: 3 (neue Zeile taucht plötzlich auf!)
COMMIT;

In diesem Fall wurde eine neue Zeile (amount = 200) von einer anderen Transaktion eingefügt und sie taucht "phantomhaft" im Ergebnis der Abfrage von Transaktion 1 auf, obwohl das Isolation Level REPEATABLE READ aktiv ist.

Wenn du Phantom Read vermeiden willst, musst du das Level SERIALIZABLE nutzen – aber das geht immer auf Kosten der Performance.

Vorteile und Nachteile von REPEATABLE READ

Das Isolation Level REPEATABLE READ ist super, wenn du sicher sein willst, dass sich Daten während einer Transaktion nicht ändern. Sobald du etwas gelesen hast, bleibt dieser Wert bis zum COMMIT gleich, selbst wenn jemand in einer anderen Transaktion versucht, etwas zu ändern.

So werden sowohl Dirty Reads als auch Non-Repeatable Reads verhindert. Du arbeitest mit genau den Daten, die du am Anfang hattest – kein überraschendes Update "on the fly". Das ist besonders praktisch, wenn du Reports erstellst oder Entscheidungen triffst, bei denen Konsistenz wichtig ist.

Auf der anderen Seite kommt REPEATABLE READ nicht mit sogenannten "Phantomen" (phantom read) klar – wenn neue Zeilen im Ergebnis einer Abfrage auftauchen, die du in derselben Transaktion schon mal gemacht hast. Außerdem kann es bei hoher Last zu Konflikten zwischen Transaktionen kommen, vor allem wenn sie oft auf die gleichen Daten zugreifen. Das kann zu Locks und Rollbacks führen, selbst wenn die Abfragen eigentlich korrekt sind.

Insgesamt ist REPEATABLE READ ein guter Kompromiss zwischen Zuverlässigkeit und Performance, aber bei hoher Konkurrenz brauchst du vielleicht noch extra Feintuning und musst aufpassen.

Nützliche Tipps und typische Fehler

  • Denk dran: Die Wahl des Isolation Levels beeinflusst die Performance. Nutze REPEATABLE READ nur, wenn du wirklich sichergehen willst, dass sich Daten nicht ändern.
  • Verwechslung zwischen REPEATABLE READ und SERIALIZABLE ist ein häufiger Fehler. Wenn du neue Zeilen bei einer erneuten Abfrage siehst, ist das bei REPEATABLE READ ganz normal.
  • Bei langen Transaktionen aufpassen: Es kann zu Lock-Konflikten kommen. Lange Transaktionen können andere Operationen blockieren.

PostgreSQL bietet verschiedene Tools, um die Transaktionsisolation zu steuern. Das Level REPEATABLE READ ist ideal, wenn du sicherstellen willst, dass bereits gelesene Daten innerhalb einer Transaktion unverändert bleiben.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION