易君召
发布于 2026-09-14 / 作者:易君召 / 1 阅读
0

Redis 事务实现方式

核心一句话:Redis 事务不是传统数据库的事务,不支持回滚;它是一组命令的批量排队、一次性串行执行,保证这一批命令连续执行不被其他客户端打断。

一、事务基础命令

Redis 事务由 4 个核心指令组成:

  1. MULTI:开启事务,标记事务开始。后续输入的命令不会立刻执行,而是放入事务队列。

  2. COMMAND:普通命令,入队,返回 QUEUED

  3. EXEC:提交事务,一次性、按顺序执行队列里所有命令。

  4. DISCARD:放弃事务,清空命令队列,退出事务状态。

额外:WATCH key [key ...]乐观锁,事务执行前监控 key,如果在 MULTI 之后、EXEC 之前被其他客户端修改,事务直接执行失败。 UNWATCH:取消监控。

二、执行流程

  1. 客户端发送 MULTI → Redis 将该客户端标记为事务状态

  2. 客户端依次发送 set/get/hset 等命令:

    • Redis不执行,仅校验语法,合法则压入事务队列,返回 QUEUED

    • 如果命令语法错误,直接报错,命令不会入队。

  3. 客户端发送 EXEC

    • Redis 拿到当前客户端的事务队列,按顺序串行执行所有命令

    • 执行期间,不会插入其他客户端的任何命令;

    • 全部执行完成后,一次性把所有结果返回客户端。

  4. 执行完毕,客户端退出事务状态。

⚠️ 重点区分两种错误:

  1. 入队前语法错误:整个事务 EXEC 会拒绝执行,全部不跑;

  2. 入队成功,但执行时运行时错误(比如对字符串做 LPOP):错误的命令失败,其他命令继续执行,不会回滚! Redis 没有 undo log,不支持回滚。

示例:

MULTI
SET a 1
INCR b   ; b不存在,入队成功,QUEUED
EXEC

执行结果:SET a 1 成功,INCR b 报错,a=1 保留,不会回滚

三、WATCH 乐观锁实现机制

WATCH 是 Redis 事务实现原子性的关键,基于版本校验的乐观锁

  1. WATCH k1 k2:Redis 记录当前客户端对 k1、k2 的版本标记(底层是每个 key 维护一个修改计数器,key 每次被修改计数器 + 1)。

  2. MULTI 开启事务,命令入队。

  3. EXEC 提交时:Redis 检查被 WATCH 的 key,如果任意 key 的计数器和 WATCH 时不一致(被别的客户端改了),直接放弃执行整个事务,返回 nil

  4. 如果没有被修改,正常执行队列命令。

WATCH 作用范围:只在当前连接生效;EXEC / DISCARD 之后会自动 UNWATCH。 缺点:WATCH 只能监控 key 是否被修改,不能做字段级监控(hash 只能监控整个 hash key)。

四、Redis 事务的特性总结(对比 ACID)

  • 原子性(部分满足): 命令队列要么全部执行(无 WATCH 冲突),要么全部不执行;但是执行中途出错,已经成功的命令不会回滚。和数据库原子性不一样。

  • 隔离性: 事务队列执行期间,其他客户端命令无法插入,天然隔离。

  • 一致性: 不支持回滚,程序逻辑错误会导致数据不一致,需要业务层处理。

  • 持久性: Redis 默认内存,RDB/AOF 只是持久化策略,事务执行成功后宕机依然可能丢数据,不保证持久。

五、Redis 事务底层实现原理

  1. 每个客户端对象 client 里有:

    • flags:标记是否处于事务状态 CLIENT_MULTI

    • multiState:事务状态结构体,里面存放命令队列数组,保存排队的命令和参数。

  2. MULTI:修改 client->flags |= CLIENT_MULTI,初始化队列。

  3. 收到普通命令:判断是事务状态,调用 queueMultiCommand 压入队列。

  4. EXEC

    • 先检查 WATCH 监控的 key 是否被改动;

    • 校验通过,遍历队列,逐个调用命令函数执行;

    • 收集返回结果;执行完清空事务队列,清除 CLIENT_MULTI 标记。

  5. DISCARD:直接清空 multiState 队列,清除事务标记。

Redis 事务本质:客户端侧的命令缓冲队列 + 服务端串行批量执行 + WATCH 版本校验,不是服务端全局事务。

六、局限性 & 替代方案

  1. 不支持回滚;

  2. 事务内不能分支逻辑(if 判断),所有命令必须预先写好入队;

  3. WATCH 有性能问题,高并发竞争场景容易事务反复失败;

  4. 不支持分布式事务。

替代方案

  1. Lua 脚本(推荐): Redis 执行 Lua 脚本时,整个脚本是原子执行,中间不会插入其他命令;脚本内可以写判断逻辑;出错可以自己控制,是 Redis 实现原子操作首选。

  2. Redis 7.0+ Redis Functions:和 Lua 类似,持久化函数。

  3. 分布式场景:用 Redlock / 业务层补偿,Redis 原生事务无法跨节点保证原子。

七、Lua 和 MULTI+WATCH 的区别

表格

方案

原子性

支持逻辑判断

错误回滚

MULTI+EXEC

队列整体执行,运行异常不回滚

❌ 不能写 if

Lua 脚本

脚本整体原子执行

✅ 脚本内 if

❌ 脚本执行一半出错,前面操作无法回滚