【玩转Redis面试】Redis事务!

📅 2026/7/16 3:30:40
【玩转Redis面试】Redis事务!
1. Redis事务生命周期开启事务使用MULTI开启一个事务命令入队列每次操作的命令都会加入到一个队列中但命令此时不会真正被执行提交事务使用EXEC命令提交事务开始顺序执行队列中的命令2. Redis事务到底是不是原子性的先看关系型数据库ACID 中关于原子性的定义原子性一个事务(transaction)中的所有操作要么全部完成要么全部不完成不会结束在中间某个环节。事务在执行过程中发生错误会被恢复(Rollback)到事务开始前的状态就像这个事务从来没有执行过一样。官方文档对事务的定义事务是一个单独的隔离操作事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中不会被其他客户端发送来的命令请求所打断。事务是一个原子操作事务中的命令要么全部被执行要么全部都不执行。EXEC 命令负责触发并执行事务中的所有命令如果客户端在使用 MULTI 开启了一个事务之后却因为断线而没有成功执行 EXEC 那么事务中的所有命令都不会被执行。另一方面如果客户端成功在开启事务之后执行 EXEC 那么事务中的所有命令都会被执行。官方认为Redis事务是一个原子操作这是站在执行与否的角度考虑的。但是从ACID原子性定义来看严格意义上讲Redis事务是非原子型的因为在命令顺序执行过程中一旦发生命令执行错误Redis是不会停止执行然后回滚数据。3. Redis为什么不支持回滚roll back在事务运行期间虽然Redis命令可能会执行失败但是Redis依然会执行事务内剩余的命令而不会执行回滚操作。如果你熟悉mysql关系型数据库事务你会对此非常疑惑Redis官方的理由如下只有当被调用的Redis命令有语法错误时这条命令才会执行失败在将这个命令放入事务队列期间Redis能够发现此类问题或者对某个键执行不符合其数据类型的操作实际上这就意味着只有程序错误才会导致Redis命令执行失败这种错误很有可能在程序开发期间发现一般很少在生产环境发现。支持事务回滚能力会导致设计复杂这与Redis的初衷相违背Redis的设计目标是功能简化及确保更快的运行速度。对于官方的这种理由有一个普遍的反对观点程序有bug怎么办但其实回归不能解决程序的bug比如某位粗心的程序员计划更新键A实际上最后更新了键B回滚机制是没法解决这种人为错误的。正因为这种人为的错误不太可能进入生产系统所以官方在设计Redis时选用更加简单和快速的方法没有实现回滚的机制。4. Redis事务失败场景有三种类型的失败场景1在事务提交之前客户端执行的命令缓存队列失败比如命令的语法错误(命令参数个数错误不支持的命令等等)。如果发生这种类型的错误Redis将向客户端返回包含错误提示信息的响应同时Redis会清空队列中的命令并取消事务。127.0.0.1:6379 set name xiaoming # 事务之前执行 OK 127.0.0.1:6379 multi # 开启事务 OK 127.0.0.1:6379 set name zhangsan # 事务中执行命令入队列 QUEUED 127.0.0.1:6379 setset name zhangsan2 # 错误的命令模拟失败场景 (error) ERR unknown command setset, with args beginning with: name, zhangsan2, 127.0.0.1:6379 exec # 提交事务发现由于上条命令的错误导致事务已经自动取消了 (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379 get name # 查询name发现未被修改 xiaoming2事务提交后开始顺序执行命令之前缓存在队列中的命令有可能执行失败。127.0.0.1:6379 multi # 开启事务 OK 127.0.0.1:6379 set name xiaoming # 设置名字 QUEUED 127.0.0.1:6379 set age 18 # 设置年龄 QUEUED 127.0.0.1:6379 lpush age 20 # 此处仅检查是否有语法错误不会真正执行 QUEUED 127.0.0.1:6379 exec # 提交事务后开始顺序执行命令第三条命令执行失败 1) OK 2) OK 3) (error) WRONGTYPE Operation against a key holding the wrong kind of value 127.0.0.1:6379 get name # 第三条命令失败没有将前两条命令回滚 xiaoming3由于乐观锁失败事务提交时将丢弃之前缓存的所有命令序列。通过开启两个redis客户端并结合watch命令模拟这种失败场景。# 客户端1 127.0.0.1:6379 set name xiaoming # 客户端1设置name OK 127.0.0.1:6379 watch name # 客户端1通过watch命令给name加乐观锁 OK # 客户端2 127.0.0.1:6379 get name # 客户端2查询name xiaoming 127.0.0.1:6379 set name zhangsan # 客户端2修改name值 OK # 客户端1 127.0.0.1:6379 multi # 客户端1开启事务 OK 127.0.0.1:6379 set name lisi # 客户端1修改name QUEUED 127.0.0.1:6379 exec # 客户端1提交事务返回空 (nil) 127.0.0.1:6379 get name # 客户端1查询name发现name没有被修改为lisi zhangsan在事务过程中监控的key被其他客户端改变则当前客户端的乐观锁失败事务提交时将丢弃所有命令缓存队列。5. Redis事务相关命令1WATCH可以为Redis事务提供 check-and-set CAS行为。被WATCH的键会被监视并会发觉这些键是否被改动过了。如果有至少一个被监视的键在 EXEC 执行之前被修改了 那么整个事务都会被取消 EXEC 返回nil-reply来表示事务已经失败。2MULTI用于开启一个事务它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令 这些命令不会立即被执行而是被放到一个队列中当 EXEC命令被调用时 所有队列中的命令才会被执行。3UNWATCH取消 WATCH 命令对所有 key 的监视一般用于DISCARD和EXEC命令之前。如果在执行 WATCH 命令之后 EXEC 命令或 DISCARD 命令先被执行了的话那么就不需要再执行 UNWATCH 了。因为 EXEC 命令会执行事务因此 WATCH 命令的效果已经产生了而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视因此这两个命令执行之后就没有必要执行 UNWATCH 了。4DISCARD当执行 DISCARD 命令时 事务会被放弃 事务队列会被清空并且客户端会从事务状态中退出。5EXEC负责触发并执行事务中的所有命令如果客户端成功开启事务后执行EXEC那么事务中的所有命令都会被执行。如果客户端在使用MULTI开启了事务后却因为断线而没有成功执行EXEC,那么事务中的所有命令都不会被执行。需要特别注意的是即使事务中有某条/某些命令执行失败了事务队列中的其他命令仍然会继续执行Redis不会停止执行事务中的命令而不会像我们通常使用的关系型数据库一样进行回滚。