Home Up PDF Prof. Dr. Ingo Claßen
Transaktionen in Postgres - ADBKT

Transaktionskonzepte in Postgres

  • Multiversion Concurrency Control, MVCC
  • Snapshot Isoloation, SI
  • Echte Serialisierbarkeit bei Snapshots, Serializable Snapshot Isoloation, SSI
  • Reader don't block Writer and Writer don't block Reader
  • Zeilensperren aber ebenfalls verfügbar, S2PL umsetzbar

Umsetzung SQL-Konsistenzstufen

Isolation Level Dirty Read Nonrepeatable Read Phantom Read Serialization Anomaly
Read uncommitted Allowed, but not in PG Possible Possible Possible
Read committed Not possible Possible Possible Possible
Repeatable read Not possible Not possible Allowed, but not in PG Possible
Serializable Not possible Not possible Not possible Not possible

Snapshot - Read Committed

Before (kid, betrag): [(100, 2), (200, 6)]

T1: START: READ_COMMITTED
T1: update konto set betrag = 3 where kid=100
T1:[(100, 3), (200, 6)]
T1: commit and close

        TRead: START: READ_COMMITTED
        TRead: select * from konto order by kid
        TRead:[(100, 3), (200, 6)]
        -- Liest alle bestätigte Änderungen
        -- von vor Beginn der Transaktion
        -- Transaktion bleibt offen

    T2: START: READ_COMMITTED
    T2: update konto set betrag = 7 where kid=200
    T2:[(100, 3), (200, 7)]
    T2: commit and close

T3: START: READ_COMMITTED
T3: insert into konto values (300, 1)
T3:[(100, 3), (200, 7), (300, 1)]
T3: commit and close

        TRead: select * from konto order by kid
        TRead:[(100, 3), (200, 7), (300, 1)]
        -- Liest alle bestätigte Änderungen
        -- die in der Zwischenzeit erfolgt sind
        TRead: commit and close

After  (kid, betrag): [(100, 3), (200, 7), (300, 1)]

Snapshot - Repeatble Read

Before (kid, betrag): [(100, 2), (200, 6)]

T1: START: READ_COMMITTED
T1: update konto set betrag = 3 where kid=100
T1:[(100, 3), (200, 6)]
T1: commit and close

        TRead: START: REPEATABLE_READ
        TRead: select * from konto order by kid
        TRead:[(100, 3), (200, 6)]
        -- Liest alle bestätigte Änderungen
        -- von vor Beginn der Transaktion
        -- Transaktion bleibt offen

    T2: START: READ_COMMITTED
    T2: update konto set betrag = 7 where kid=200
    T2:[(100, 3), (200, 7)]
    T2: commit and close

T3: START: READ_COMMITTED
T3: insert into konto values (300, 1)
T3:[(100, 3), (200, 7), (300, 1)]
T3: commit and close

        TRead: select * from konto order by kid
        TRead:[(100, 3), (200, 6)]
        -- Änderungen von T2 und T3 waren
        -- zu Begin von TREAD nicht bestätigt
        -- Sind daher nicht sichtbar
        -- TREAD hat REPEATABLE_READ
        TRead: commit and close

After  (kid, betrag): [(100, 3), (200, 7), (300, 1)]

Lost Update - Read Comitted

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: select * from personal order by pid
T1:[(100, 40000), (200, 50000)]

    T2: START: READ_COMMITTED
    T2: select * from personal order by pid
    T2:[(100, 40000), (200, 50000)]
    -- T1 und T2 haben denselben DB-Zustand gelesen

T1: update personal set gehalt=41000 where pid=100
T1:[(100, 41000), (200, 50000)]
T1: commit and close

    T2: update personal set gehalt=42000 where pid=100
    T2:[(100, 42000), (200, 50000)]
    -- T2 überschreibt die Änderung von T1
    -- Ist möglich, da READ_COMMITTED
    T2: commit and close

After  (pid, gehalt): [(100, 42000), (200, 50000)]

Lost Update - Repeatable Read

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: REPEATABLE_READ
T1: select * from personal order by pid
T1:[(100, 40000), (200, 50000)]

    T2: START: REPEATABLE_READ
    -- T2 ist auf REPEATABLE_READ gesetzt
    T2: select * from personal order by pid
    T2:[(100, 40000), (200, 50000)]
    -- T1 und T2 haben denselben DB-Zustand gelesen

T1: update personal set gehalt=41000 where pid=100
T1:[(100, 41000), (200, 50000)]
T1: commit and close

    -- T2 versucht update
    T2: update personal set gehalt=42000 where pid=100
    T2: SerializationFailure
    -- Erkennt Änderung von T1
    -- Bricht ab, um Lost Update zu vermeiden
    -- Neustart erforderlich
    -- Basiert dann auf von T1 geänderten Werten

After  (pid, gehalt): [(100, 41000), (200, 50000)]

Write Skew - Repeatable Read

Before (kid, betrag): [(100, 80), (200, 50)]

T1: START: REPEATABLE_READ
T1: select * from konto order by kid
T1:[(100, 80), (200, 50)]
-- T1 stellt fest, dass Konto 100 und 200
-- zusammen 130 Euro haben

    T2: START: REPEATABLE_READ
    T2: select * from konto order by kid
    T2:[(100, 80), (200, 50)]
    -- T2 stellt ebenfalls fest, dass Konto 100 und 200
    -- zusammen 130 Euro haben

T1: update konto set betrag = betrag -90 where kid=100
T1:[(100, -10), (200, 50)]
-- T1 hebt 90 Euro von Konto 100 ab, da negativer 
-- Kontostand durch Konto 200 gedeckt ist
T1: commit and close

    T2: update konto set betrag = betrag -50 where kid=200
    T2:[(100, 80), (200, 0)]
    -- T2 hebt 50 Euro von Konto 200 ab
    -- Ist ok, da Konto 200 den Betrag deckt
    T2: commit and close

-- Invariante verletzt, da Summe
-- auf beiden Konten nicht negativ sein darf
After  (kid, betrag): [(100, -10), (200, 0)]

Write Skew - SERIALIZABLE - T2->T1

Before (kid, betrag): [(100, 80), (200, 50)]

T1: START: SERIALIZABLE
-- T1 ist nun auf SERIALIZABLE eingestellt
T1: select * from konto order by kid
T1:[(100, 80), (200, 50)]
-- T1 stellt fest, dass Konto 100 und 200
-- zusammen 130 Euro haben

    T2: START: SERIALIZABLE
    -- T2 ist nun auf SERIALIZABLE eingestellt
    T2: select * from konto order by kid
    T2:[(100, 80), (200, 50)]
    -- T2 stellt ebenfalls fest, dass Konto 100 und 200
    -- zusammen 130 Euro haben

T1: update konto set betrag = betrag -90 where kid=100
T1:[(100, -10), (200, 50)]
-- T1 hebt 90 Euro von Konto 100 ab, da negativer 
-- Kontostand durch Konto 200 gedeckt ist
T1: commit and close

    -- T2 versucht 50 Euro von Konto 200 abzuheben
    T2: update konto set betrag = betrag -50 where kid=200
    T2: SerializationFailure
    -- Ist nicht möglich, da T2 vom gelesenen Zustand
    -- von Konto 100 abhängt, dieser wurde aber geändert
    -- Neustart erforderlich
    -- Basiert dann auf von T1 geänderten Werten

-- Invariante nicht verletzt
After  (kid, betrag): [(100, -10), (200, 50)]

Write Skew - SERIALIZABLE - T2-/-> T1

Before (kid, betrag): [(100, 80), (200, 50)]

T1: START: SERIALIZABLE
-- T1 immer noch auf SERIALIZABLE
T1: select * from konto order by kid
T1:[(100, 80), (200, 50)]
-- T1 stellt fest, dass Konto 100 und 200
-- zusammen 130 Euro haben

T1: update konto set betrag = betrag -90 where kid=100
T1:[(100, -10), (200, 50)]
-- T1 hebt 90 Euro von Konto 100 ab, da negativer 
-- Kontostand durch Konto 200 gedeckt ist
T1: commit and close

    T2: START: SERIALIZABLE
    -- Hat den Zustand von Konto 100 nicht gelesen
    T2: update konto set betrag = betrag -50 where kid=200
    T2:[(100, -10), (200, 0)]
    -- Update möglich, da die Änderung von Konto 100
    -- für T2 nicht relevant ist
    T2: commit and close

-- Invariante verletzt
After  (kid, betrag): [(100, -10), (200, 0)]

Serialisierungsfehler

Before (cls, val): [(1, 10), (1, 20), (2, 100), (2, 200)]

T1: START: REPEATABLE_READ
T1: insert into cv select 2, sum(val) from cv where cls=1
T1:[(1, 10), (1, 20), (2, 30), (2, 100), (2, 200)]
T1: commit and close

    T2: START: REPEATABLE_READ
    T2: insert into cv select 1, sum(val) from cv where cls=2
    T2:[(1, 10), (1, 20), (1, 330), (2, 30), (2, 100), (2, 200)]
    T2: commit and close

-- Erst T1 komplett, dann T2
After  (cls, val): [(1, 10), (1, 20), (1, 330), (2, 30), (2, 100), (2, 200)]

**********

Before (cls, val): [(1, 10), (1, 20), (2, 100), (2, 200)]

    T2: START: REPEATABLE_READ
    T2: insert into cv select 1, sum(val) from cv where cls=2
    T2:[(1, 10), (1, 20), (1, 300), (2, 100), (2, 200)]
    T2: commit and close

T1: START: REPEATABLE_READ
T1: insert into cv select 2, sum(val) from cv where cls=1
T1:[(1, 10), (1, 20), (1, 300), (2, 100), (2, 200), (2, 330)]
T1: commit and close

-- Erst T2 komplett, dann T1
After  (cls, val): [(1, 10), (1, 20), (1, 300), (2, 100), (2, 200), (2, 330)]

**********

Before (cls, val): [(1, 10), (1, 20), (2, 100), (2, 200)]

T1: START: REPEATABLE_READ
T1: insert into cv select 1, sum(val) from cv where cls=2
T1:[(1, 10), (1, 20), (1, 300), (2, 100), (2, 200)]

    T2: START: REPEATABLE_READ
    T2: insert into cv select 2, sum(val) from cv where cls=1
    T2:[(1, 10), (1, 20), (2, 30), (2, 100), (2, 200)]
    T2: commit and close

T1: commit and close

-- Jetzt T1 und T2 verzahnt
-- REPEATABLE_READ
-- Ergebnis entspricht keiner serialisierten Ausführung
After  (cls, val): [(1, 10), (1, 20), (1, 300), (2, 30), (2, 100), (2, 200)]

**********

Before (cls, val): [(1, 10), (1, 20), (2, 100), (2, 200)]

T1: START: SERIALIZABLE
T1: insert into cv select 1, sum(val) from cv where cls=2
T1:[(1, 10), (1, 20), (1, 300), (2, 100), (2, 200)]

    T2: START: SERIALIZABLE
    T2: insert into cv select 2, sum(val) from cv where cls=1
    T2:[(1, 10), (1, 20), (2, 30), (2, 100), (2, 200)]
    T2: commit and close

T1: SerializationFailure

-- Erneut T1 und T2 verzahnt
-- SERIALIZABLE
-- Serialisierungsfehler wird erkannt
After  (cls, val): [(1, 10), (1, 20), (2, 30), (2, 100), (2, 200)]

Update Conflicts 1 - READ COMMITTED

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: update personal set gehalt= gehalt + 1000 where pid=100
T1:[(100, 41000), (200, 50000)]
-- T1 hat Datensatz 100 geändert, aber noch nicht bestätigt
-- T1 hält Sperre auf Datensatz 100

    -- T2 versucht auch, Datensatz 100 zu ändern
    -- Bleibt stehen, wegen Sperre von T1
    T2: START: READ_COMMITTED
    T2: update personal set gehalt= gehalt + 2000 where pid=100

T1: rollback and close
-- T1 macht Rollback, Sperre fällt weg

    -- T2 kann weiterlaufen
    T2:[(100, 42000), (200, 50000)]
    T2: commit and close

After  (pid, gehalt): [(100, 42000), (200, 50000)]

**********

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: update personal set gehalt= gehalt + 1000 where pid=100
T1:[(100, 41000), (200, 50000)]
-- T1 hat Datensatz 100 geändert, aber noch nicht bestätigt
-- T1 hält Sperre auf Datensatz 100

    -- T2 versucht auch, Datensatz 100 zu ändern
    -- Bleibt stehen, wegen Sperre von T1
    T2: START: READ_COMMITTED
    T2: update personal set gehalt= gehalt + 2000 where pid=100

T1: commit and close
-- T1 macht Commit, Sperre fällt weg

    -- T2 kann weiterlaufen
    -- Aktualisiert wegen READ_COMMITTED den Zustand von 100
    -- Macht update auf geänderten Zustand
    T2:[(100, 43000), (200, 50000)]
    T2: commit and close

After  (pid, gehalt): [(100, 43000), (200, 50000)]

**********

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: delete from personal where pid=100
T1:[(200, 50000)]
-- T1 hat Datensatz 100 gelöscht, aber noch nicht bestätigt
-- T1 hält Sperre auf Datensatz 100

    -- T2 versucht, Datensatz 100 zu ändern
    -- Bleibt stehen, wegen Sperre von T1
    T2: START: READ_COMMITTED
    T2: update personal set gehalt= gehalt + 2000 where pid=100

T1: commit and close
-- T1 macht Commit, Sperre fällt weg

    -- T2 kann weiterlaufen
    -- Aktualisiert wegen READ_COMMITTED den Zustand von 100,
    -- d.h. die Löschung
    -- Update von T2 läuft ins Leere
    T2:[(200, 50000)]
    T2: commit and close

After  (pid, gehalt): [(200, 50000)]

Update Conflicts 1 - REPEATABLE READ

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: update personal set gehalt= gehalt + 1000 where pid=100
T1:[(100, 41000), (200, 50000)]
-- T1 hat Datensatz 100 geändert, aber noch nicht bestätigt
-- T1 hält Sperre auf Datensatz 100

    -- T2 versucht auch, Datensatz 100 zu ändern
    -- Bleibt stehen, wegen Sperre von T1
    -- T2 ist auf REPEATABLE_READ
    T2: START: REPEATABLE_READ
    T2: update personal set gehalt= gehalt + 2000 where pid=100

T1: rollback and close
-- T1 macht Rollback, Sperre fällt weg

    -- T2 kann weiterlaufen
    -- Keine Veränderung von Datensatz 100 wegen Rollback
    -- T2 kann erfolgreich abgeschlossen werden
    T2:[(100, 42000), (200, 50000)]
    T2: commit and close

After  (pid, gehalt): [(100, 42000), (200, 50000)]

**********

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: update personal set gehalt= gehalt + 1000 where pid=100
T1:[(100, 41000), (200, 50000)]
-- T1 hat Datensatz 100 geändert, aber noch nicht bestätigt
-- T1 hält Sperre auf Datensatz 100

    -- T2 versucht auch, Datensatz 100 zu ändern
    -- Bleibt stehen, wegen Sperre von T1
    -- T2 ist auf REPEATABLE_READ
    T2: START: REPEATABLE_READ
    T2: update personal set gehalt= gehalt + 2000 where pid=100

T1: commit and close
-- T1 macht Commit, Sperre fällt weg

    -- T2 kann weiterlaufen
    -- Erkennt Änderung auf Datensatz 100
    -- Bricht ab
    T2: SerializationFailure

After  (pid, gehalt): [(100, 41000), (200, 50000)]

**********

Before (pid, gehalt): [(100, 40000), (200, 50000)]

T1: START: READ_COMMITTED
T1: delete from personal where pid=100
T1:[(200, 50000)]
-- T1 hat Datensatz 100 gelöscht, aber noch nicht bestätigt
-- T1 hält Sperre auf Datensatz 100

    -- T2 versucht, Datensatz 100 zu ändern
    -- Bleibt stehen, wegen Sperre von T1
    -- T2 ist auf REPEATABLE_READ
    T2: START: REPEATABLE_READ
    T2: update personal set gehalt= gehalt + 2000 where pid=100

T1: commit and close
-- T1 macht Commit, Sperre fällt weg

    -- T2 kann weiterlaufen
    -- Erkennt Änderung (Löschung) auf Datensatz 100
    -- Bricht ab
    T2: SerializationFailure

After  (pid, gehalt): [(200, 50000)]

Update Conflicts 2 - READ COMMITTED

Before (wsid, hits): [(100, 9), (200, 10)]

T1: START: READ_COMMITTED
T1: update website set hits = hits + 1
T1:[(100, 10), (200, 11)]
-- Hits werden für Site 100 und 200 um 1 erhöht
-- Sperren auf beiden Datensätzen

    -- T2 versucht, Datensätze mit 10 Hits zu löschen
    -- Bleibt stehen, wegen Sperren von T1
    -- Datensatz 200 ist in der Löschmenge
    T2: START: READ_COMMITTED
    T2: delete from website where hits = 10

T1: commit and close
-- T1 macht Commit, Sperren fallen weg

    -- T2 kann weiterlaufen
    -- Aktualisiert Löschmenge, d.h. Datensatz 200
    -- Hat jetzt 11 Hits, erfüllt die Löschbedingung nicht mehr
    -- Löschung läuft ins Leere
    T2:[(100, 10), (200, 11)]
    T2: commit and close

After  (wsid, hits): [(100, 10), (200, 11)]

Update Conflicts 2 - REPEATABLE READ

Before (wsid, hits): [(100, 9), (200, 10)]

T1: START: READ_COMMITTED
T1: update website set hits = hits + 1
T1:[(100, 10), (200, 11)]
-- Hits werden für Site 100 und 200 um 1 erhöht
-- Sperren auf beiden Datensätzen

    -- T2 versucht, Datensätze mit 10 Hits zu löschen
    -- Bleibt stehen, wegen Sperren von T1
    -- Datensatz 200 ist in der Löschmenge
    T2: START: REPEATABLE_READ
    T2: delete from website where hits = 10

T1: commit and close
-- T1 macht Commit, Sperren fallen weg

    -- T2 kann weiterlaufen
    -- Datensatz 200 aus der Löschmenge wurde geändert
    -- Bricht ab
    T2: SerializationFailure

After  (wsid, hits): [(100, 10), (200, 11)]

Write Skew Avoidance - with Locks

Before (kid, betrag): [(100, 80), (200, 50)]

T1: START: READ_COMMITTED
T1: select * from konto order by kid for update
T1:[(100, 80), (200, 50)]
-- T1 liest Kontostände
-- T1 hält Sperre auf Datensatz 100 und 200

    -- T2 versucht auch, Sperren auf Datensätze 100 und 200 zu setzen
    -- Bleibt stehen, wegen Sperren von T2
    T2: START: READ_COMMITTED
    T2: select * from konto order by kid for update

T1: update konto set betrag = betrag -90 where kid=100
T1:[(100, -10), (200, 50)]
T1: commit and close
-- T1 hebt 90 Euro von Konto 100 ab
-- Bestätigt und entfernt alle Sperren

    -- T2 läuft weiter
    -- Stellt fest, dass Abhebung von 50 Euro nicht gedeckt ist
    T2:[(100, -10), (200, 50)]
    T2: commit and close

After  (kid, betrag): [(100, -10), (200, 50)]