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