Begriff
MVCC (Multi-Version Concurrency Control)
Warum wichtig?
Dieser Begriff ist ein Knoten im SengakujiWorks-Wissensnetz. Nutze Level 0 für die erste Einordnung, Level 1 für Praxis, Level 2 für technische Struktur und Level 3 für Grenzen, Fallstricke und Expertenkontext.
Klassische Datenbanken nutzen Locks (Sperren). Wenn ich lese ("SELECT * FROM User"), darfst du nicht schreiben ("UPDATE User"). Du musst warten. Das ist langsam. MVCC ist die Lösung: Readers never block Writers. Writers never block Readers. Jede Transaktion sieht eine eigene Version der Datenbank (Snapshot). Wenn ich Zeile X ändere, überschreibe ich sie nicht. Ich erstelle eine neue Version von Zeile X (Version 2). Die alte Version (Version 1) bleibt da. Andere, die vor mir angefangen haben zu lesen, sehen weiterhin Version 1. Ich sehe Version 2. Erst wenn alle alten Leser fertig sind, wird Version 1 gelöscht (Vacuum). Das ist der Standard in PostgreSQL, Oracle, MySQL (InnoDB).
Merksatz: Eine Methode zur Steuerung des gleichzeitigen Zugriffs auf Datenbanken, bei der statt Sperren (Locks) mehrere Versionen eines Datensatzes vorgehalten werden, sodass Lese- und Schreibvorgänge sich nicht gegenseitig blockieren.
Als Entwickler:
Verstehe, dass SELECT COUNT(*) in PostgreSQL langsam ist.
Weil Postgres jede Zeile prüfen muss: "Ist diese Version für mich sichtbar?"
Es gibt keinen globalen Zähler (wie in MyISAM).
Und verstehe VACUUM:
Wenn du viele Updates machst, entstehen viele "tote" Versionen (Bloat).
Postgres muss die bereinigen (Garbage Collection). Wenn VACUUM nicht läuft, wird die DB langsam.
1. Transaction ID (XID)
Jede Transaktion bekommt eine Nummer (XID).
Jede Zeile hat xmin (Wer hat mich erstellt?) und xmax (Wer hat mich gelöscht?).
Eine Zeile ist sichtbar für mich, wenn:
xmincommitted und < meine XID.xmaxnull oder > meine XID (noch nicht gelöscht für mich). Das ermöglicht "Time Travel Queries" (in Oracle "Flashback Query").
2. Write Skew
Ein MVCC-spezifisches Problem. T1 liest A. T2 liest B. T1 schreibt B (basierend auf A). T2 schreibt A (basierend auf B). Beide committen erfolgreich. Aber die logische Bedingung ("Summe A+B > 0") ist verletzt. Das passiert in "Snapshot Isolation", aber nicht in "Serializable". PostgreSQL bietet "Serializable Snapshot Isolation" (SSI), um das zu verhindern.
1. PostgreSQL Bloat und Transaction ID Wraparounds
MVCC hat in Systemen wie Postgres den gravierenden Architektur-Haken der fehlenden In-Place Updates (Append Only Tables).
Ein "Update" baut immer einen komplett neuen Tuple ans Dateiende, das gigantische Mengen von toten (Zombie) Tuplen ansammelt. Läuft der Autovacuum Daemon im Produktionseinsatz nicht aggressiv genug mit, fragmentieren riesige Lücken tief in der Hard Disk. Da Sequence Scans all diese toten Bytes physisch mitlesen, kollabiert der Index/RAM Cache.
Wichtiger ist das katastrophale Transaction ID Wraparound Risiko der XID Limits. Postgres nutzt 32-Bit Integer (4 Milliarden). Wenn eine Datenbank-Server Farm diese Zahl ausbucht, springt der Zähler wieder auf "0". Alte Zeilen aus der Vergangenheit (ID: 3.9Mld) sehen auf einmal "neuere" XIDs (0004) – die gesamte Datenbank reißt sofort in sich zusammen und zerstört logisch die gesamte Relationalität. Das System verweigert Schreib-Ops massiv hart mit "Stop!" und muss zwingend über den Vacuum Freeze-Prozess die IDs in der Night-Shift rekonditionieren!
2. InnoDB Undo Logs vs PostgreSQL Versioning
Es gibt zwei grundlegend verschiedene Architekturen von MVCC. Postgres speichert die alte Version ($V_{1}$) radikal physikalisch direkt im identischen Table-Datenblock wie die neue ($V_{2}$) nebeneinander ab. MySQL (InnoDB) oder Oracle hingegen speichern immer nur die aktuellste Form ($V_{2}$) in der Table als In-Place. Wohin dann zur Zeitreise mit den Alt-Werten? In extrem komprimierte, dedizierte Undo-Logs Disk-Bereiche. MySQL's Design (Undo Segments) glänzt spektakulär, da Primary-Key Suchen nur einen Tuple pro Key-Hash vorfinden. Wird ein "Time-Travel"-Read vom Isolations-Layer getriggert, rekonstruiert (roll back) MySQL via Memory-Pointern den veralteten Block im RAM durch die History-Log-Deltas dynamisch on-the-fly zurück. Das System verbrennt etwas Read-Cycle CPU für alte Transactions, belässt aber die Disk Tablespace Storage brutal defragmentiert.
3. Snapshot Isolation Anomaly (Write Skew Fixes)
Snapshot Isolation (SI) im MVCC behebt "Dirty Reads" und "Non-Repeatable Reads", stürzt jedoch in die mathematische Schattenwelt des Write Skews (Schreib-Asymmetrie).
Arzt 1 und Arzt 2 loggen sich exakt simultan ins Schichtsystem ein (Requirement: "Immer mindestens 1 Arzt auf Dienst"). Beide lesen per MVCC den Zustand: "2 Ärzte sind On-Call."
Beide sehen eine logisch perfekte Welt und loggen sich im gleichen Snapshot simultan aus, ändern also je ihren Status auf false ab, und das System schluckt klaglos parallel (da sie unterschiedliche Zeilen ändern, kein Row-Lock greift). Das Constraint (mindestens 1) scheitert fatal.
Lösung: Serializable Snapshot Isolation (SSI) (Papiere ab 2008 von Cahill et al.).
SSI nutzt MVCC weiterhin lock-free, hinterlässt aber unsichtbare Read Locks (SIREAD) "Spinnfäden" im RAM-Gedächtnis. Das DBMS detektiert gefährliche zyklische Read-Write-Abhängigkeiten im Conflict-Graph im Augenblick der Festplatten-Commits und rollt eine der beiden Transaktionen nachträglich per "Serialization Failure" brutal ab.
Quick-Check
Vorteil?
Massive Performance bei gemischten Workloads (OLTP). Ein Backup (Dump) kann stundenlang laufen und blockiert keine einzige Schreib-Transaktion.Nachteil?
Speicherplatz. Man braucht mehr Platz für die Versionen. Und CPU für Garbage Collection (Vacuum).Isolationslevel?
MVCC implementiert meist "Snapshot Isolation" oder "Repeatable Read".