Redis 事务

Redis 事务(Transaction)完全攻略

“半事务”机制原子执行 + 隔离性,但 无回滚

适合 批量操作、乐观锁、状态机切换,不适合复杂业务逻辑


一、Redis 事务核心特点

特性说明
命令MULTIEXECDISCARD
原子性所有命令一起执行或都不执行
隔离性命令入队时不执行,EXEC 时一次性执行
无回滚出错不回滚,前面命令已生效
无 ACID 完整性AI,无 C(一致性)、D(持久性)保证
乐观锁配合 WATCH 实现

Redis 事务 ≠ 数据库事务,更像 批量原子操作


二、事务命令全表

命令说明示例
MULTI开启事务MULTI
EXEC执行事务EXEC
DISCARD取消事务DISCARD
WATCH key [key ...]监控 key(乐观锁)WATCH balance:1001
UNWATCH取消监控UNWATCH

三、事务执行流程

MULTI
INCR counter
HSET user:1 score 100
EXEC
# 返回: 1) 2  2) OK
graph TD
    A[客户端] -->|MULTI| B[Redis 事务队列]
    B -->|命令入队| C[INCR, HSET, ...]
    C -->|EXEC| D[一次性原子执行]
    D -->|成功| E[返回结果数组]
    D -->|失败| F[部分成功,前面命令已生效]

四、核心实战场景


1. 批量操作(提升性能)

MULTI
SET key1 "v1"
SET key2 "v2"
INCR counter
EXEC

比 Pipeline 多了 原子性,但性能略低


2. 转账(乐观锁 + WATCH)

# 步骤1:监控账户余额
WATCH balance:1001 balance:1002

# 步骤2:读取余额
GET balance:1001  → 1000
GET balance:1002  → 500

# 步骤3:开启事务
MULTI
DECRBY balance:1001 100
INCRBY balance:1002 100
EXEC
# 如果中途被修改 → EXEC 返回 nil

失败重试机制(客户端实现)

def transfer():
    while True:
        r.watch('balance:1001', 'balance:1002')
        a = int(r.get('balance:1001') or 0)
        b = int(r.get('balance:1002') or 0)
        if a < 100:
            r.unwatch()
            return False
        pipe = r.multi()
        pipe.decrby('balance:1001', 100)
        pipe.incrby('balance:1002', 100)
        result = pipe.execute()
        if result:  # 成功
            return True
        # 失败 → 重试

3. 状态机切换

MULTI
SET order:1001:status "paid"
DEL order:1001:cart
EXEC

4. 库存扣减(结合 Lua 更佳)

MULTI
DECRBY stock:1001 1
ZADD recent:sales:1001 {timestamp} "user:2001"
EXEC

推荐用 Lua 脚本替代复杂事务


五、事务 vs Pipeline vs Lua 脚本 对比

对比项事务 (MULTI)PipelineLua 脚本
原子性支持不支持支持
回滚不支持不支持不支持
性能中等最快
逻辑复杂不支持不支持支持
错误处理部分执行全部执行全部失败
推荐场景简单原子批量高性能批量复杂业务逻辑

结论

  • 简单原子操作 → 事务
  • 高性能批量 → Pipeline
  • 复杂逻辑 → Lua 脚本

六、事务失败场景解析

场景结果
语法错误整个事务失败(EXEC 前发现)
运行时错误(如 INCR 非数字)前面命令已执行,错误命令返回错误
WATCH 冲突EXEC 返回 nil
客户端断开事务被丢弃
MULTI
SET a 1
INCR a    # 运行时错误
SET b 2
EXEC
# 返回: 1) OK  2) ERR value is not integer  3) OK

七、WATCH 乐观锁详解

graph TD
    A[客户端1] -->|WATCH k| R[Redis]
    B[客户端2] -->|SET k v2| R
    A -->|MULTI| R
    A -->|SET k v3| R
    A -->|EXEC| R -->|返回 nil| A

最佳实践:

  • WATCH + MULTI + 重试
  • 监控 最小必要 key
  • 避免长时间 WATCH

八、一键速查表

# 基本事务
MULTI
SET a 1
INCR counter
EXEC

# 乐观锁转账
WATCH balance:1
GET balance:1
MULTI
DECRBY balance:1 100
INCRBY balance:2 100
EXEC   # 若返回 nil → 重试

# 取消
DISCARD
UNWATCH

九、客户端代码示例

Python (redis-py)

import redis
r = redis.Redis()

# 简单事务
pipe = r.multi()
pipe.set('k1', 'v1')
pipe.incr('counter')
result = pipe.execute()
print(result)  # [True, 2]

# 乐观锁转账
def transfer(r, from_key, to_key, amount):
    while True:
        with r.pipeline() as pipe:
            try:
                pipe.watch(from_key, to_key)
                balance = int(pipe.get(from_key) or 0)
                if balance < amount:
                    pipe.unwatch()
                    return False
                pipe.multi()
                pipe.decrby(from_key, amount)
                pipe.incrby(to_key, amount)
                pipe.execute()
                return True
            except redis.WatchError:
                continue  # 重试

十、常见问题与解决方案

问题原因解决方案
部分命令失败运行时错误用 Lua 脚本
死锁WATCH 太多减少监控 key
性能低事务太大拆分 + Pipeline
事务返回 nilWATCH 冲突客户端重试

十一、高并发事务优化

1. 短事务原则

# 错误:1000 个命令
MULTI
HSET ... x1000
EXEC

# 正确:分批
for i in range(0, 1000, 100):
    pipe = r.multi()
    for j in range(100):
        pipe.hset(...)
    pipe.execute()

2. Lua 替代复杂事务

EVAL "
  if redis.call('exists', KEYS[1]) == 1 then
    local stock = tonumber(redis.call('get', KEYS[1]))
    if stock >= 1 then
      redis.call('decr', KEYS[1])
      return 1
    end
  end
  return 0
" 1 stock:1001

完成!你已精通 Redis 事务!

# 一行命令体验事务全流程
redis-cli <<EOF
WATCH counter
GET counter
MULTI
INCR counter
EXEC
UNWATCH
EOF

下一步推荐

  1. Redis Lua 脚本高级编程
  2. Pipeline 高性能批量操作
  3. 分布式锁(Redisson vs Lua)

需要我送你

  • “高并发转账系统(事务 + 重试)”
  • “库存扣减原子性方案(事务 vs Lua)”
  • “状态机事务模板”

回复:转账 | 扣库存 | 状态机 即可!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部