Redis Transaktionen mit MULTI/EXEC: Moeglichkeiten und Grenzen
AI generated
SET
TTL
Redis · Transaktionen · MULTI/EXEC · WATCH
Redis Transaktionen mit MULTI/EXEC
Moeglichkeiten und Grenzen im Detail

Redis Transaktionen queuen Befehle mit MULTI und fuehren sie atomar mit EXEC aus, ohne dass ein anderer Client dazwischenfunkt. Wer jedoch glaubt, MULTI/EXEC verhalte sich wie eine SQL-Transaktion mit Rollback bei Laufzeitfehlern, baut auf eine falsche Annahme, die im Produktivbetrieb zu inkonsistenten Daten fuehren kann.

13 Min. Lesezeit MULTI · EXEC · DISCARD · WATCH Redis 6.x · 7.x · redis-cli

1. Was Redis Transaktionen wirklich sind

Eine Redis Transaktion ist im Kern etwas anderes, als der Begriff aus der relationalen Datenbankwelt vermuten laesst. MULTI startet einen Warteschlangen-Modus, in dem alle nachfolgenden Befehle nicht sofort ausgefuehrt, sondern in eine Queue eingereiht werden. Erst EXEC fuehrt alle gequeueten Befehle nacheinander und ohne Unterbrechung durch andere Clients aus. Diese Isolation ist die zentrale Garantie von Redis Transaktionen: kein anderer verbundener Client kann zwischen zwei Befehlen einer Transaktion eigene Befehle einschieben.

Diese Garantie funktioniert, weil Redis Single-Threaded arbeitet und Befehle grundsaetzlich sequenziell abarbeitet. Eine Redis Transaktion nutzt diese Eigenschaft aus, um mehrere Befehle als zusammenhaengenden Block auszufuehren, ohne dass ein Locking-Mechanismus im klassischen Sinn noetig waere. Das unterscheidet Redis Transaktionen grundlegend von SQL-Transaktionen, die auf Undo-Logs, Rollback-Segmenten und mehreren gleichzeitig laufenden Threads mit Sperrverwaltung basieren.

Wichtig fuer das Verstaendnis: Redis Transaktionen garantieren Isolation und teilweise Atomaritaet, aber keine vollstaendige ACID-Semantik. Es gibt keine Durability-Garantie im Sinne eines Write-Ahead-Logs, das erst nach der Transaktion geschrieben wird, und es gibt vor allem keine automatische Rueckabwicklung, wenn ein Befehl innerhalb der Transaktion zur Laufzeit fehlschlaegt. Diese Eigenschaft ist der haeufigste Missverstaendnis-Punkt bei Entwicklern, die aus der relationalen Welt kommen.

2. Command Queueing: wie MULTI Befehle sammelt

Sobald ein Client MULTI sendet, wechselt die Verbindung in einen speziellen Zustand. Jeder danach gesendete Befehl wird von Redis nicht ausgefuehrt, sondern nur syntaktisch validiert und in eine interne Warteschlange fuer genau diese Verbindung gelegt. Redis antwortet auf jeden gequeueten Befehl sofort mit QUEUED, was fuer den Client bereits eine erste wichtige Rueckmeldung ist: Der Befehl wurde syntaktisch akzeptiert und wartet auf die Ausfuehrung.

Dieses Queueing ist rein lokal fuer die jeweilige Verbindung. Andere Clients koennen waehrend dieser Zeit weiterhin normal mit Redis arbeiten, ihre Befehle werden nicht blockiert. Erst wenn EXEC gesendet wird, verlaesst Redis den Queueing-Modus, sperrt intern kurzzeitig die Ausfuehrung fuer andere Clients und arbeitet die komplette Warteschlange sequenziell ab, bevor der naechste Client an der Reihe ist. Diese kurze, aber garantierte Unterbrechungsfreiheit ist die eigentliche Kernleistung von Redis Transaktionen.


# Start a transaction: commands are queued, not executed yet
redis-cli> MULTI
OK

redis-cli> SET account:1001:balance 500
QUEUED

redis-cli> INCRBY account:1001:balance -100
QUEUED

redis-cli> INCRBY account:1002:balance 100
QUEUED

# Nothing has happened yet, all three commands wait in the queue
redis-cli> GET account:1001:balance
(nil)

# EXEC runs the entire queue atomically, without interruption
redis-cli> EXEC
1) OK
2) (integer) 400
3) (integer) 100

3. EXEC und DISCARD in der Praxis

EXEC fuehrt alle gequeueten Befehle in der Reihenfolge aus, in der sie eingereiht wurden, und liefert ein Array mit den einzelnen Antworten zurueck, in exakt derselben Reihenfolge. Waehrend der Ausfuehrung kann kein anderer Client Befehle zwischen die einzelnen Schritte schieben, weil Redis den Event-Loop fuer die Dauer der Transaktion nicht an andere Verbindungen abgibt. Das ist der entscheidende Unterschied zu einer einfachen Folge von Einzelbefehlen ohne MULTI, bei der theoretisch andere Clients dazwischenfunken koennten.

DISCARD bricht eine laufende Transaktion vor EXEC ab und leert die Warteschlange, ohne dass ein einziger Befehl ausgefuehrt wird. Das ist nuetzlich, wenn eine Anwendung waehrend des Queueings feststellt, dass eine Vorbedingung nicht erfuellt ist, und die gesamte Transaktion sauber verwerfen moechte, statt sie auszufuehren. Nach DISCARD verlaesst die Verbindung den Transaktionsmodus vollstaendig und arbeitet wieder normal.


# DISCARD aborts the queue entirely, nothing gets executed
redis-cli> MULTI
OK
redis-cli> DEL session:temp
QUEUED
redis-cli> DISCARD
OK

# The connection is back to normal command mode
redis-cli> EXEC
(error) ERR EXEC without MULTI

4. WATCH und optimistisches Locking

WATCH ist der Mechanismus, der Redis Transaktionen erst fuer nebenlaeufige Anwendungsfaelle wirklich nuetzlich macht. Mit WATCH key markiert ein Client einen oder mehrere Keys zur Beobachtung, bevor MULTI gestartet wird. Aendert sich der Wert eines beobachteten Keys durch einen anderen Client zwischen WATCH und EXEC, bricht EXEC die gesamte Transaktion automatisch ab und liefert stattdessen nil zurueck. Kein einziger gequeueter Befehl wird dann ausgefuehrt.

Dieses Verhalten implementiert klassisches optimistisches Locking, auch bekannt als Compare-And-Swap-Muster. Statt einen Lock zu erwerben, der andere Clients blockiert, geht die Anwendung optimistisch davon aus, dass keine Konflikte auftreten, und prueft erst bei EXEC, ob diese Annahme noch gueltig war. Das ist besonders effizient bei niedriger Konflikthaeufigkeit, weil kein Client jemals wirklich blockiert wird, sondern im schlimmsten Fall die Transaktion wiederholen muss.

Der typische Ablauf: WATCH auf den relevanten Key, dann den aktuellen Wert lesen und die neue Berechnung vorbereiten, dann MULTI starten, die berechneten Befehle queuen und EXEC aufrufen. Schlaegt EXEC fehl, weil der Key sich zwischenzeitlich geaendert hat, wiederholt die Anwendung den kompletten Zyklus in einer Retry-Schleife. UNWATCH entfernt alle Beobachtungen manuell, wird aber ohnehin automatisch nach jedem EXEC oder DISCARD aufgerufen.


# Optimistic locking: watch the key, then queue commands based on its value
redis-cli> WATCH inventory:sku-4711:stock
OK

redis-cli> GET inventory:sku-4711:stock
"12"

# Application logic decides: deduct 3 units if stock is sufficient
redis-cli> MULTI
OK
redis-cli> DECRBY inventory:sku-4711:stock 3
QUEUED

# If another client modified inventory:sku-4711:stock in between,
# EXEC aborts and returns nil, the application must retry the cycle
redis-cli> EXEC
(nil)

# Manually clear all watched keys without running a transaction
redis-cli> UNWATCH
OK

5. Praxisbeispiel: Kontostand sicher transferieren

Ein klassisches Beispiel fuer Redis Transaktionen ist der Transfer eines Betrags zwischen zwei Kontostaenden, die als einfache Keys gespeichert sind. Ohne Transaktion koennte ein Absturz oder ein Netzwerkfehler zwischen dem Abziehen vom Ursprungskonto und dem Gutschreiben auf dem Zielkonto dazu fuehren, dass Geld verschwindet. Mit MULTI/EXEC werden beide Operationen als ein zusammenhaengender Block ausgefuehrt, der von keinem anderen Client unterbrochen werden kann.

Kombiniert mit WATCH auf das Ursprungskonto stellt die Anwendung zusaetzlich sicher, dass der Kontostand zwischen der Pruefung und der eigentlichen Transaktion nicht durch eine parallele Anfrage veraendert wurde, etwa durch eine gleichzeitige Abbuchung. Erst wenn WATCH keine Aenderung meldet und EXEC erfolgreich durchlaeuft, gilt der Transfer als abgeschlossen. Diese Kombination aus Isolation durch MULTI/EXEC und Konflikterkennung durch WATCH ist das Standardmuster fuer nebenlaeufigkeitssichere Redis Transaktionen.

6. Warum es kein Rollback bei Laufzeitfehlern gibt

Der wichtigste Unterschied zwischen Redis Transaktionen und SQL-Transaktionen betrifft das Verhalten bei Fehlern, die erst waehrend der Ausfuehrung eines Befehls auftreten. Wenn innerhalb einer Transaktion ein Befehl auf den falschen Datentyp angewendet wird, etwa INCR auf einen String-Wert, der keine Zahl ist, schlaegt genau dieser eine Befehl fehl. Alle anderen Befehle der Transaktion werden dennoch ausgefuehrt, es gibt keinen automatischen Rollback der bereits erfolgreich ausgefuehrten Schritte.

Dieses Verhalten ist eine bewusste Design-Entscheidung der Redis-Entwickler, keine Einschraenkung aus Zeitmangel. Die Begruendung lautet: Laufzeitfehler in Redis entstehen fast immer durch Programmierfehler, etwa falsche Datentypen, und nicht durch transiente Systemzustaende, die ein Rollback sinnvoll rueckgaengig machen koennte. Ein Rollback-Mechanismus wuerde erhebliche Komplexitaet und Performance-Kosten verursachen, fuer ein Problem, das durch Tests im Entwicklungsprozess bereits vor der Produktion erkannt werden sollte.


# Runtime error inside a transaction does NOT roll back other commands
redis-cli> SET counter "not-a-number"
OK

redis-cli> MULTI
OK
redis-cli> SET other:key "value-one"
QUEUED
redis-cli> INCR counter
QUEUED
redis-cli> SET another:key "value-two"
QUEUED

redis-cli> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK

# other:key and another:key were both set, no rollback happened
redis-cli> GET other:key
"value-one"
redis-cli> GET another:key
"value-two"

7. Zwei Fehlerarten: Queueing-Fehler vs. Laufzeitfehler

Redis unterscheidet strikt zwischen zwei Fehlerkategorien innerhalb einer Transaktion. Queueing-Fehler entstehen, wenn ein Befehl bereits waehrend des Einreihens als ungueltig erkannt wird, etwa ein unbekannter Befehlsname oder eine falsche Anzahl von Argumenten. Diese Fehler markiert Redis intern, und wenn danach EXEC aufgerufen wird, verweigert Redis die gesamte Ausfuehrung mit einem EXECABORT-Fehler. In diesem Fall wird tatsaechlich kein einziger Befehl der Transaktion ausgefuehrt, was manchmal faelschlich als generelles Rollback-Verhalten missverstanden wird.

Laufzeitfehler hingegen, wie im vorherigen Abschnitt gezeigt, werden erst bei der tatsaechlichen Ausfuehrung durch EXEC erkannt, weil Redis den Datentyp eines Keys erst zur Laufzeit prueft, nicht beim Queueing. Diese Fehler fuehren nicht zum Abbruch der restlichen Transaktion. Fuer Entwickler bedeutet das: Wer sich auf automatische Konsistenz verlaesst, muss aktiv pruefen, ob einzelne Befehle in der EXEC-Antwort einen Fehler enthalten, und bei Bedarf selbst eine kompensierende Logik implementieren.

8. Wann Lua-Skripte die bessere Wahl sind

Fuer Anwendungsfaelle, die eine konditionale Logik zwischen mehreren Befehlen benoetigen, etwa "lies einen Wert, und fuehre abhaengig vom Ergebnis unterschiedliche Schreiboperationen aus", stossen MULTI/EXEC an eine strukturelle Grenze: Innerhalb einer Transaktion koennen keine Lesewerte ausgewertet werden, um zu entscheiden, welcher naechste Befehl gequeued wird, denn alle Befehle muessen vor der Ausfuehrung feststehen. Genau hier sind serverseitige Lua-Skripte mit EVAL die passendere Loesung, weil sie echte bedingte Logik direkt im Redis-Prozess ausfuehren koennen.

Ein Lua-Skript ist ausserdem atomarer als eine MULTI/EXEC-Transaktion im Sinne der Fehlerbehandlung, weil ein Laufzeitfehler im Skript dessen weitere Ausfuehrung sofort stoppt, statt wie bei MULTI/EXEC die restlichen Befehle weiterlaufen zu lassen. Fuer einfache, unbedingte Befehlsfolgen ohne Zwischenauswertung bleibt MULTI/EXEC dennoch die einfachere und lesbarere Wahl, insbesondere in Kombination mit WATCH fuer optimistisches Locking.

9. MULTI/EXEC im Vergleich zu anderen Mechanismen

Die Wahl zwischen MULTI/EXEC, WATCH-basiertem optimistischem Locking und Lua-Skripten haengt vom konkreten Anwendungsfall ab. Die folgende Tabelle stellt die zentralen Eigenschaften gegenueber.

Eigenschaft MULTI/EXEC MULTI/EXEC mit WATCH Lua-Skript (EVAL)
Isolation gegen andere Clients Ja Ja Ja
Konflikterkennung bei Nebenlaeufigkeit Nein Ja, per Retry-Schleife Nicht noetig, laeuft synchron
Bedingte Logik zwischen Befehlen Nicht moeglich Nicht innerhalb der Transaktion Voll unterstuetzt
Rollback bei Laufzeitfehler Kein Rollback Kein Rollback Skript stoppt bei Fehler
Netzwerk-Roundtrips Mehrere (pro Befehl) Mehrere plus WATCH/GET Ein einziger Roundtrip

Fuer einfache, unbedingte Befehlsketten ist MULTI/EXEC ausreichend und gut lesbar. Sobald Nebenlaeufigkeit relevant wird, ergaenzt WATCH die notwendige Konflikterkennung. Sobald bedingte Logik ins Spiel kommt oder die Anzahl der Netzwerk-Roundtrips kritisch wird, ist ein Lua-Skript die robustere und meist auch performantere Alternative.

10. Zusammenfassung

Redis Transaktionen mit MULTI und EXEC garantieren Isolation gegenueber anderen Clients, indem alle gequeueten Befehle ohne Unterbrechung sequenziell ausgefuehrt werden. WATCH ergaenzt optimistisches Locking und erlaubt es, Transaktionen bei nebenlaeufigen Aenderungen sicher abzubrechen und zu wiederholen. Die entscheidende Grenze: Laufzeitfehler innerhalb einer Transaktion fuehren zu keinem Rollback der bereits erfolgreich ausgefuehrten Befehle, nur Queueing-Fehler verhindern die gesamte Ausfuehrung ueber EXECABORT.

Wer eine echte bedingte Logik zwischen mehreren Redis-Operationen benoetigt oder maximale Atomaritaet bei Fehlern erwartet, sollte auf Lua-Skripte mit EVAL zurueckgreifen. Fuer einfache, unbedingte Befehlsfolgen bleibt MULTI/EXEC in Kombination mit WATCH das direkteste und am einfachsten zu verstehende Werkzeug fuer nebenlaeufigkeitssichere Operationen in Redis.

Redis Transaktionen mit MULTI/EXEC, das Wichtigste auf einen Blick

Command Queueing

MULTI startet den Warteschlangen-Modus, jeder Befehl wird mit QUEUED bestaetigt, ausgefuehrt wird erst mit EXEC.

WATCH fuer optimistisches Locking

Beobachtete Keys, die sich vor EXEC aendern, brechen die Transaktion automatisch ab. Anwendung muss den Zyklus wiederholen.

Kein Rollback

Laufzeitfehler eines Befehls stoppen nicht die restlichen Befehle der Transaktion. Fehlerpruefung liegt beim Client.

Alternative bei Bedarf

Fuer bedingte Logik zwischen Befehlen sind Lua-Skripte mit EVAL die robustere und meist schnellere Wahl.

11. FAQ: Redis Transaktionen mit MULTI/EXEC

1Was passiert bei MULTI?
Verbindung wechselt in Warteschlangen-Modus, Befehle werden mit QUEUED bestaetigt.
2Gibt es Rollback wie bei SQL?
Nein, Laufzeitfehler stoppen nicht die restlichen Befehle der Transaktion.
3Wann bricht EXEC ab?
Nur bei Queueing-Fehlern, dann EXECABORT, kein Befehl wird ausgefuehrt.
4Was macht WATCH?
Markiert Keys zur Beobachtung, Aenderung vor EXEC bricht die Transaktion ab.
5WATCH vs. klassischer Lock?
Optimistisches Locking, kein Blockieren, bei Konflikt muss die Anwendung wiederholen.
6Bedingte Logik in Transaktionen?
Nicht moeglich mit MULTI/EXEC, dafuer ein Lua-Skript mit EVAL nutzen.
7Was macht DISCARD?
Verwirft die Warteschlange vollstaendig, kein Befehl wird ausgefuehrt.
8Sind Transaktionen ACID-konform?
Nur teilweise, Isolation ja, klassisches Rollback und Durability nein.
9Fehlgeschlagenen Befehl erkennen?
EXEC liefert ein Array, ein fehlgeschlagener Befehl enthaelt dort ein Error-Objekt.
10MULTI/EXEC oder Lua bevorzugen?
Einfache Faelle MULTI/EXEC mit WATCH, bedingte Logik oder maximale Atomaritaet Lua-Skript.