Redis事务失败的处理机制与处理方案
一、Redis 事务执行流程全景图
二、事务执行失败的三大类型及处理机制
1. 语法错误(编译时错误)
特征:命令在入队时立即检测到错误
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key1 value1
QUEUED
127.0.0.1:6379> INCRBY key1 100 # 对非数字值操作
(error) ERR value is not an integer or out of range # 立即报错
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
- ✅ 处理机制:事务整体回滚,所有命令不执行
2. 运行时错误(执行时错误)
特征:命令语法正确但执行时违反约束
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET balance "abc" # 字符串值
QUEUED
127.0.0.1:6379> INCRBY balance -100 # 对字符串执行减法
QUEUED
127.0.0.1:6379> EXEC
1) OK # 第一条命令成功
2) (error) ERR value is not an integer or out of range # 第二条失败
- ✅ 处理机制:失败命令后的操作继续执行,无自动回滚
3. 资源限制错误
特征:超出系统限制导致的失败
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET small_key "val"
QUEUED
127.0.0.1:6379> SET big_key "巨型值..." # 超过maxmemory
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) OOM command not allowed when used memory > 'maxmemory'
- ✅ 处理机制:错误命令失败,后续命令正常执行
三、事务原子性的本质剖析
Redis 事务的原子性 ≠ 传统数据库原子性:
核心结论:
Redis 仅保证 命令入队的原子性 和 执行的隔离性,不保证操作结果的原子性
四、生产环境事务失败处理方案
方案1:WATCH 命令实现乐观锁
Jedis jedis = new Jedis("localhost");
String key = "balance:1001";while (true) {jedis.watch(key);int balance = Integer.parseInt(jedis.get(key));if (balance < 6000) break; // 检查条件Transaction tx = jedis.multi();tx.set(key, String.valueOf(balance - 6000));List<Object> results = tx.exec();if (results != null) { break; // 执行成功}// 如果失败则重试
}
jedis.unwatch();
方案2:Lua 脚本(原子操作)
-- 原子转账脚本
local from_acc = KEYS[1]
local to_acc = KEYS[2]
local amount = tonumber(ARGV[1])local from_balance = tonumber(redis.call('GET', from_acc))
if from_balance < amount thenreturn {err = "Insufficient funds"}
endredis.call('DECRBY', from_acc, amount)
redis.call('INCRBY', to_acc, amount)return {status = "SUCCESS"}