How to fix ERR DISCARD without MULTI in Redis
This error occurs when you call DISCARD without first starting a transaction with MULTI. DISCARD is only valid within an active transaction context, and calling it outside that context causes Redis to return this error.
Quick fix
Fixes ERR DISCARD without MULTI
MULTI -> Queue commands -> (EXEC or DISCARD)- Verify transaction state before calling DISCARD
- Check for concurrent connection usage
- Implement proper exception handling in transactions
- Use client libraries with transaction abstractions
MULTI -> Queue commands -> (EXEC or DISCARD)ERR DISCARD without MULTI
What this error means
In Redis, transactions are initiated with the MULTI command, which puts the connection into transaction mode. During this mode, subsequent commands are queued rather than executed immediately. The DISCARD command is used to abort the transaction and discard all queued commands, restoring the connection to normal state. When you call DISCARD without a corresponding active MULTI command, Redis returns this error because there is no transaction context to abort. This typically happens due to transaction state mismanagement, connection sharing issues, or race conditions in concurrent environments.
Typical symptoms
- Redis client returns "(error) ERR DISCARD without MULTI" response
- Transaction handling fails unexpectedly in production
- Errors appear intermittently in concurrent/multi-threaded applications
- Connection state becomes inconsistent after transaction operations
Common causes
- Calling DISCARD in normal mode instead of transaction mode
- Transaction already completed or rolled back before DISCARD is called
- Concurrent requests on shared Redis connections causing command interleaving
- Exception thrown during MULTI causing transaction to auto-commit, then DISCARD called
- Connection timeout or failure leaving transaction state undefined
- Improper error handling causing DISCARD to execute when transaction is not active
How to fix "ERR DISCARD without MULTI"
Always ensure MULTI has been called on the connection before attempting DISCARD. The proper transaction flow is:
MULTI -> Queue commands -> (EXEC or DISCARD)Never call DISCARD on a connection that is not in transaction mode.
If using connection pooling or sharing connections across multiple threads/requests, ensure proper synchronization. Each logical transaction should use a dedicated connection:
# Bad: shared connection
redis_conn = get_shared_connection()
redis_conn.multi()
# ... another thread uses redis_conn here ...
redis_conn.discard() # Error: DISCARD without MULTI
# Good: connection per transaction
with redis.pipeline(transaction=True) as pipe:
pipe.multi()
pipe.set('key', 'value')
pipe.execute()Wrap transaction logic in try-catch blocks to prevent DISCARD from executing if an exception occurs:
try:
redis_conn.multi()
redis_conn.set('key1', 'value1')
redis_conn.incr('counter')
redis_conn.execute()
except Exception as e:
# DISCARD only if transaction is still active
try:
redis_conn.discard()
except ResponseError:
# Transaction already aborted, connection is clean
pass
raiseModern Redis client libraries provide abstractions that handle MULTI/EXEC/DISCARD automatically:
# Using redis-py pipeline (manages transaction state)
with redis_client.pipeline(transaction=True) as pipe:
pipe.set('key1', 'value1')
pipe.incr('counter')
results = pipe.execute()
# Using Jedis in Java
try (Jedis jedis = jedisPool.getResource()) {
Pipeline pipeline = jedis.pipelined();
pipeline.watch("key");
pipeline.multi();
pipeline.set("key", "value");
List<Object> results = pipeline.exec(); // Handles DISCARD on watch failure
}Implement logging and monitoring to detect transaction state issues:
import logging
import redis
logger = logging.getLogger(__name__)
class MonitoredRedis:
def __init__(self, client):
self.client = client
self.in_transaction = False
def multi(self):
logger.debug("Starting transaction")
self.in_transaction = True
return self.client.multi()
def discard(self):
if not self.in_transaction:
logger.error("DISCARD called without MULTI")
raise RuntimeError("DISCARD without MULTI")
self.in_transaction = False
return self.client.discard()Advanced notes
In multi-threaded environments, consider using Lua scripts instead of MULTI/EXEC. Lua scripts are atomic and eliminate transaction state management complexity: redis.eval("redis.call('set', KEYS[1], ARGV[1]); return redis.call('incr', KEYS[2])", 2, 'key1', 'counter', 'value1'). For distributed systems with connection pooling, prefer client library transaction wrappers (pipelines) that automatically manage transaction context. Note that Redis transactions do not support rollbacks—once EXEC completes, all commands have been applied. This is by design for performance reasons.
Frequently asked questions
- What does "ERR DISCARD without MULTI" mean?
- In Redis, transactions are initiated with the MULTI command, which puts the connection into transaction mode. During this mode, subsequent commands are queued rather than executed immediately. The DISCARD command is used to abort the transaction and discard all queued commands, restoring the connection to normal state.
- How do I fix "ERR DISCARD without MULTI"?
- This error occurs when you call DISCARD without first starting a transaction with MULTI. DISCARD is only valid within an active transaction context, and calling it outside that context causes Redis to return this error.
- What causes "ERR DISCARD without MULTI"?
- Calling DISCARD in normal mode instead of transaction mode Transaction already completed or rolled back before DISCARD is called Concurrent requests on shared Redis connections causing command interleaving
- Is the "ERR DISCARD without MULTI" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚Redis Official Documentation
- 📖Redis Docs - DISCARD Command(Official Docs)
- 📖Redis Docs - MULTI Command(Official Docs)
- 📖Redis Docs - Transactions(Official Docs)
- 📖Redis Docs - How Transactions Work(Official Docs)
- 🐙