Redis事务 #
一、事务概述 #
1.1 什么是事务 #
Redis事务是一组命令的集合:
- 原子性:事务中的命令要么全部执行,要么全部不执行
- 隔离性:事务执行期间不会被其他命令打断
- 无回滚:不支持回滚(命令错误不会撤销已执行的命令)
text
Redis事务流程:
┌─────────────────────────────────────────────────────────┐
│ 客户端 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ MULTI │ │
│ │ SET key1 value1 │ │
│ │ SET key2 value2 │ │
│ │ INCR counter │ │
│ │ EXEC │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Redis Server │ │
│ │ 1. 接收MULTI,开始事务 │ │
│ │ 2. 命令入队,不执行 │ │
│ │ 3. 接收EXEC,执行所有命令 │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 Redis事务特点 #
text
Redis事务特点:
┌─────────────────────────────────────────────┐
│ 1. 命令入队 │
│ - MULTI后命令不立即执行 │
│ - 命令进入队列等待 │
│ │
│ 2. 原子执行 │
│ - EXEC后一次性执行所有命令 │
│ - 执行期间不会被其他命令打断 │
│ │
│ 3. 无回滚 │
│ - 命令错误不会撤销已执行的命令 │
│ - 需要业务层面处理错误 │
│ │
│ 4. 乐观锁 │
│ - WATCH实现乐观锁 │
│ - 检测键是否被修改 │
└─────────────────────────────────────────────┘
二、基本操作 #
2.1 开启和执行事务 #
bash
# MULTI: 开启事务
MULTI
# OK
# 命令入队
SET key1 "value1"
# QUEUED
SET key2 "value2"
# QUEUED
INCR counter
# QUEUED
# EXEC: 执行事务
EXEC
# 1) OK
# 2) OK
# 3) (integer) 1
# 验证结果
GET key1
# "value1"
GET key2
# "value2"
GET counter
# "1"
2.2 取消事务 #
bash
# DISCARD: 取消事务
MULTI
SET key1 "value1"
# QUEUED
SET key2 "value2"
# QUEUED
DISCARD
# OK
# 命令不会执行
GET key1
# (nil)
2.3 事务中的错误 #
bash
# 情况1:命令语法错误(入队时检测)
MULTI
SET key1 "value1"
# QUEUED
INCR key1 key2 # 错误的参数数量
# (error) ERR wrong number of arguments
SET key2 "value2"
# QUEUED
EXEC
# (error) EXECABORT Transaction discarded because of previous errors.
# 整个事务被丢弃
# 情况2:命令执行错误(执行时检测)
MULTI
SET key1 "value1"
# QUEUED
INCR key1 # key1是字符串,不能INCR
# QUEUED
SET key2 "value2"
# QUEUED
EXEC
# 1) OK
# 2) (error) ERR value is not an integer or out of range
# 3) OK
# 只有INCR失败,其他命令成功执行
GET key1
# "value1"
GET key2
# "value2"
三、WATCH乐观锁 #
3.1 WATCH基本使用 #
bash
# WATCH: 监视一个或多个键
WATCH key1 key2
# OK
# 如果被监视的键被其他客户端修改
# 事务将执行失败
MULTI
SET key1 "new_value1"
# QUEUED
SET key2 "new_value2"
# QUEUED
EXEC
# 如果key1或key2被修改过,返回nil
# (nil)
# 如果没有被修改,正常执行
# 1) OK
# 2) OK
3.2 WATCH实现乐观锁 #
bash
# 场景:转账
# 用户A向用户B转账100元
# 实现乐观锁转账
WATCH user:a:balance user:b:balance
# 获取当前余额
balance_a = GET user:a:balance # 假设为500
balance_b = GET user:b:balance # 假设为200
# 检查余额是否足够
if balance_a >= 100:
MULTI
SET user:a:balance balance_a - 100
SET user:b:balance balance_b + 100
result = EXEC
if result is nil:
# 余额被其他客户端修改,重试
retry()
else:
# 余额不足
UNWATCH
3.3 UNWATCH取消监视 #
bash
# UNWATCH: 取消所有监视
WATCH key1 key2
# OK
UNWATCH
# OK
# 之后的事务不再受监视影响
MULTI
SET key1 "value"
EXEC
# 1) OK
3.4 WATCH注意事项 #
text
WATCH注意事项:
1. WATCH在MULTI之前执行
┌─────────────────────────────────────────────┐
│ ✓ WATCH key │
│ MULTI │
│ ... │
│ EXEC │
│ │
│ ✗ MULTI │
│ WATCH key # 错误 │
│ EXEC │
└─────────────────────────────────────────────┘
2. EXEC/DISCARD会自动UNWATCH
┌─────────────────────────────────────────────┐
│ WATCH key │
│ MULTI │
│ ... │
│ EXEC # 自动取消WATCH │
└─────────────────────────────────────────────┘
3. WATCH监视整个键
┌─────────────────────────────────────────────┐
│ WATCH hash:key │
│ # 监视整个hash,任何字段变化都会触发 │
└─────────────────────────────────────────────┘
四、事务应用场景 #
4.1 原子操作 #
bash
# 原子性设置多个键
MULTI
SET user:1001:name "John"
SET user:1001:age 25
SET user:1001:email "john@example.com"
EXEC
4.2 库存扣减 #
bash
# 使用WATCH实现库存扣减
WATCH product:1001:stock
stock = GET product:1001:stock
if stock > 0:
MULTI
DECR product:1001:stock
result = EXEC
if result is nil:
# 库存被修改,重试
retry()
else:
# 扣减成功
return success
else:
UNWATCH
return out_of_stock
4.3 秒杀场景 #
bash
# 秒杀抢购
WATCH item:1001:stock item:1001:users
stock = GET item:1001:stock
if stock > 0:
MULTI
DECR item:1001:stock
SADD item:1001:users user:1001
result = EXEC
if result is nil:
retry()
else:
return success
else:
UNWATCH
return sold_out
4.4 转账场景 #
bash
# 转账
WATCH account:a account:b
balance_a = GET account:a
balance_b = GET account:b
amount = 100
if balance_a >= amount:
MULTI
DECRBY account:a amount
INCRBY account:b amount
result = EXEC
if result is nil:
retry()
else:
return success
else:
UNWATCH
return insufficient_balance
五、事务与Lua脚本对比 #
5.1 对比 #
text
事务 vs Lua脚本:
┌─────────────────────────────────────────────────────────┐
│ 事务 │
├─────────────────────────────────────────────────────────┤
│ 优点: │
│ - 简单易用 │
│ - 不需要学习Lua │
│ │
│ 缺点: │
│ - 无法获取中间结果 │
│ - 无法做条件判断 │
│ - 无回滚机制 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Lua脚本 │
├─────────────────────────────────────────────────────────┤
│ 优点: │
│ - 完整的原子性 │
│ - 可以获取中间结果 │
│ - 可以做条件判断 │
│ - 更灵活 │
│ │
│ 缺点: │
│ - 需要学习Lua │
│ - 脚本可能阻塞 │
└─────────────────────────────────────────────────────────┘
5.2 选择建议 #
text
选择建议:
使用事务:
- 简单的批量操作
- 不需要条件判断
- 配合WATCH实现乐观锁
使用Lua脚本:
- 需要条件判断
- 需要获取中间结果
- 复杂的原子操作
六、最佳实践 #
6.1 错误处理 #
bash
# 检查EXEC返回值
result = EXEC
if result is nil:
# WATCH的键被修改,重试
retry()
else:
for r in result:
if is_error(r):
# 处理错误
handle_error(r)
6.2 重试机制 #
python
# Python示例
def execute_with_retry(redis, max_retries=3):
for i in range(max_retries):
try:
redis.watch('key1', 'key2')
# 获取当前值
val1 = redis.get('key1')
val2 = redis.get('key2')
# 开启事务
pipe = redis.pipeline()
pipe.multi()
pipe.set('key1', new_val1)
pipe.set('key2', new_val2)
# 执行
result = pipe.execute()
return result
except WatchError:
if i == max_retries - 1:
raise
continue
6.3 避免大事务 #
bash
# 不推荐:大事务
MULTI
# ... 1000个命令
EXEC
# 推荐:拆分事务
MULTI
# ... 10个命令
EXEC
MULTI
# ... 10个命令
EXEC
七、总结 #
事务命令:
| 命令 | 说明 |
|---|---|
| MULTI | 开启事务 |
| EXEC | 执行事务 |
| DISCARD | 取消事务 |
| WATCH | 监视键 |
| UNWATCH | 取消监视 |
事务特点:
| 特点 | 说明 |
|---|---|
| 原子性 | 命令一次性执行 |
| 隔离性 | 执行期间不被打断 |
| 无回滚 | 命令错误不撤销 |
| 乐观锁 | WATCH实现 |
应用场景:
| 场景 | 实现方式 |
|---|---|
| 原子操作 | MULTI + EXEC |
| 库存扣减 | WATCH + MULTI |
| 秒杀 | WATCH + MULTI |
| 转账 | WATCH + MULTI |
下一步,让我们学习Redis Lua脚本!
最后更新:2026-03-27