How it works
The explanation, with examples you can run.
MULTI and EXEC
MULTI starts queuing; each command replies QUEUED; EXEC runs them all in order, with no other client's command in between.
> SET balance:1 100OK> MULTIOK> DECRBY balance:1 30QUEUED> INCRBY balance:2 30QUEUED> EXEC1) (integer) 702) (integer) 30There is no rollback. A command that fails when it runs fails alone, and the rest still apply (the OK and QUEUED replies are left out here):
> MULTI> SET k v> LPUSH k x> INCR counter> EXEC1) OK2) (error) WRONGTYPE Operation against a key holding the wrong kind of value3) (integer) 1(Errors Redis can detect while queuing, such as a misspelt command, make EXEC discard the whole transaction.) The Redis docs explain this as a choice for simplicity and speed; runtime errors are usually programming errors that tests should catch. DISCARD abandons a queued transaction.
The bigger limit: queued commands have no results yet, so you cannot read a value and decide what to write inside MULTI.
WATCH: check-and-set
WATCH marks keys; if any of them changes before your EXEC, the transaction is not run and EXEC returns nil. Reading a value, then writing based on it, becomes safe with a retry loop:
# Client A # Client B> WATCH stock:sku1> GET stock:sku1"5" > DECR stock:sku1 (integer) 4> MULTI> DECR stock:sku1QUEUED> EXEC(nil) # A's transaction did not run; re-read and retryThis is optimistic: no one waits, but under heavy contention on one key many attempts fail and retry.
Lua scripts
A script runs on the server, atomically: while it runs, no other command executes. It can read, branch and write, so it handles cases MULTI cannot:
> EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:invoice:7 3f9a1c(integer) 1Pass every key the script touches in KEYS (not built from strings inside the script), so that Redis Cluster can route it and check that the keys share a slot. Load scripts once with SCRIPT LOAD and call them by hash with EVALSHA, or, since Redis 7, use functions (FUNCTION LOAD, FCALL), which are stored with the data and replicated.
Atomic also means blocking. A slow script stops every other client. After the busy threshold (5 seconds by default) Redis starts answering other clients with a BUSY error, and SCRIPT KILL can stop a script that has not yet written anything; one that has written can only be stopped by shutting the server down without saving. Keep scripts short and bounded.
Which to use
- Several independent writes that should all happen together:
MULTI/EXEC(or a pipeline wrapped in it). - Read, then write based on what you read, with little contention:
WATCHwith a retry loop. - Conditional logic, or heavy contention on one key: a Lua script or function.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.