The "ERR EXEC without MULTI" error occurs when attempting to execute EXEC without first starting a transaction with MULTI. This is typically caused by connection switching, premature DISCARD calls, or connection pool issues in client libraries.
Redis transactions are entered using the MULTI command, which queues subsequent commands without executing them. The EXEC command then executes all queued commands atomically. When EXEC is called without a corresponding MULTI, Redis returns the "ERR EXEC without MULTI" error because there is no active transaction to execute. This error commonly occurs in three scenarios: directly calling EXEC without MULTI in the Redis CLI, attempting to execute EXEC after issuing DISCARD (which exits the transaction), or experiencing connection multiplexing issues where MULTI and EXEC commands from different transactions get routed to different Redis connections.
Ensure your code calls MULTI before any transaction commands, and only calls EXEC after queueing the desired commands.
Correct pattern:
MULTI # Starts transaction
SET key1 val1 # Gets queued
SET key2 val2 # Gets queued
EXEC # Executes all queued commandsIncorrect pattern:
SET key1 val1 # Executed immediately
EXEC # Error: ERR EXEC without MULTIDISCARD aborts a transaction and resets the connection state. If you call DISCARD, the transaction is ended and you cannot call EXEC.
Incorrect:
MULTI
SET key1 val1
DISCARD # Transaction ended
EXEC # Error: ERR EXEC without MULTIIf you need to cancel a transaction, only call DISCARD and then start a new MULTI/EXEC block if needed.
If using Spring Data Redis or similar libraries, ensure all MULTI/EXEC commands use the same Redis connection. Use SessionCallback or similar transaction support to bind operations to a single connection.
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public <K, V> List<Object> execute(RedisOperations<K, V> operations)
throws DataAccessException {
operations.multi();
operations.opsForValue().set("key1", "value1");
operations.opsForValue().set("key2", "value2");
return operations.exec();
}
});This ensures all commands are executed on the same connection instance.
For clients like vert-x3, Lettuce, or Jedis, verify that:
- The same connection is used for MULTI, all queued commands, and EXEC
- Connection pooling doesn't multiplex commands from different transactions onto the same connection
- Enable debugging to trace which connection handles each command
If using load testing, reduce concurrency initially to verify the basic pattern works. Under high load (30+ rps), connection switching becomes more likely.
Connection multiplexing: Some client libraries multiplex multiple operations across a shared connection pool. This can cause MULTI and EXEC commands from different logical transactions to interleave on the same Redis connection, resulting in EXEC being issued without its corresponding MULTI.
WATCH and optimistic locking: When using WATCH before MULTI for optimistic locking, ensure the WATCH, MULTI, operations, and EXEC all happen on the same connection. If any watched key is modified between WATCH and EXEC, the transaction is aborted and EXEC returns null.
Cluster mode limitations: Redis Cluster does not support the WATCH command. For atomic operations in cluster mode, use Lua scripts instead of MULTI/EXEC.
High-load scenarios: Under heavy load, issues with connection switching become more apparent. Load testing with 30+ requests per second often reveals connection pool bugs that don't appear in normal operation.
ERR fsync error
How to fix "ERR fsync error" in Redis
CLUSTERDOWN The cluster is down
How to fix 'CLUSTERDOWN The cluster is down' in Redis
ERR Job for redis-server.service failed because a timeout was exceeded
Job for redis-server.service failed because a timeout was exceeded
ERR Unbalanced XREAD list of streams
How to fix "ERR Unbalanced XREAD list" in Redis
ERR syntax error
How to fix "ERR syntax error" in Redis