真实面经题目 · 原创解析
Redis是否支持事务?
Redis 支持事务,但它的事务更像“命令批量排队并按顺序一次性执行”的机制,而不是关系型数据库里带自动回滚、强一致约束检查的事务。面试回答要强调 MULTI、EXEC、DISCARD、WATCH 的作用,以及入队错误和执行期错误的处理差异。
真实面经题目 · 原创解析
Redis 支持事务,但它的事务更像“命令批量排队并按顺序一次性执行”的机制,而不是关系型数据库里带自动回滚、强一致约束检查的事务。面试回答要强调 MULTI、EXEC、DISCARD、WATCH 的作用,以及入队错误和执行期错误的处理差异。
Redis 是支持事务的,核心命令是 MULTI、EXEC、DISCARD 和 WATCH。客户端执行 MULTI 后进入事务状态,后续命令不会立刻执行,而是被放入队列;执行 EXEC 时,Redis 会把队列里的命令按入队顺序一次性执行,中间不会穿插其他客户端命令。DISCARD 用于放弃已经入队的命令并退出事务状态。需要注意的是,Redis 事务和 MySQL 这类关系型数据库事务不完全一样,它不提供执行期自动回滚:如果某条命令已经成功执行,后面某条命令因为类型不匹配等原因失败,前面成功的结果不会撤销。Redis 会区分两类错误:命令入队阶段就能发现的语法错误、参数数量错误,通常会导致 EXEC 拒绝执行整个事务;而执行阶段才暴露的错误,只会让该命令返回错误,其他命令仍会继续执行。WATCH 提供乐观锁能力,用来监视一个或多个 key,如果这些 key 在 EXEC 前被其他客户端修改,EXEC 会返回空结果,事务不执行。Lua 脚本也常被拿来实现更强的原子逻辑,但它是单段脚本原子执行,不等同于传统事务,也不具备自动回滚能力。
Redis 支持事务,但面试中不能只回答“支持”。更准确的说法是:Redis 事务提供命令队列化和顺序原子执行的能力,保证 EXEC 阶段队列内命令不会被其他客户端命令打断;但它不提供关系型数据库那种完整的事务语义,尤其没有执行失败后的自动回滚。这个差异是本题的核心,答题时要主动把“支持事务”和“不等同于 MySQL 事务”区分开。
Redis 事务通常从 MULTI 开始,客户端进入事务上下文。之后发送的命令不会马上修改数据,而是返回 QUEUED 并进入事务队列。客户端发送 EXEC 后,Redis 才会按照入队顺序依次执行这些命令,并把每条命令的结果组成数组返回。DISCARD 则用于放弃事务队列,清空已经排队的命令,并退出事务状态。这个流程体现的是批处理和顺序执行,而不是复杂的提交日志、锁管理和回滚段机制。
Redis 事务的原子性要按场景理解。EXEC 执行时,事务队列里的命令会连续执行,其他客户端的命令不会插入到这个执行过程里,因此从调度层面看,它具有不被打断的原子执行特征。但如果队列中某条命令执行失败,Redis 不会把已经执行成功的命令撤销,所以它不具备关系型数据库常说的“要么全部成功、要么全部回滚”的原子性。面试中如果直接说 Redis 事务完全 ACID,容易被追问击穿。
Redis 事务错误要分成命令入队错误和执行期错误。入队错误通常是语法错误、命令不存在、参数数量不对等,这些问题在 EXEC 之前就能发现,Redis 会标记事务出错,最终 EXEC 时拒绝执行整个事务队列。执行期错误则不同,例如对字符串执行列表操作,命令只有真正运行时才发现类型不匹配,这时该命令返回错误,但同一事务里其他命令仍会继续执行,已经成功的写入不会自动撤销。
WATCH 用于给 Redis 事务补充乐观锁语义。客户端可以先 WATCH 一个或多个 key,再读取这些 key 并基于读到的值计算新结果,随后 MULTI 排队写命令,最后 EXEC 提交。如果被 WATCH 的 key 在 EXEC 之前被其他客户端修改,EXEC 会返回空结果,表示事务没有执行,调用方需要重新读取并重试。WATCH 不是悲观锁,不会阻塞其他客户端修改数据,它解决的是并发修改检测问题。
Lua 脚本常被用来替代 Redis 事务完成更复杂的原子操作,因为脚本在 Redis 内执行期间不会被其他命令打断,可以把读取、判断和写入封装在一次服务端执行中,避免客户端多次往返和 WATCH 重试逻辑。但 Lua 脚本也不是关系型数据库事务:脚本中已经执行的写操作不会因为后续运行错误而自动回滚,并且长脚本会阻塞 Redis 处理其他请求,所以它适合短小、确定、原子性要求强的逻辑。
不能简单说完全满足。Redis 事务有顺序执行和 EXEC 期间不被打断的特征,但没有关系型数据库那种执行失败自动回滚能力。持久性还取决于 RDB、AOF 及其刷盘策略,所以面试中更稳妥的回答是:Redis 提供轻量事务机制,但不等同于传统 ACID 事务。
要看错误发生阶段。如果是 EXEC 前就能发现的入队错误,事务通常不会执行;如果是 EXEC 执行期间的类型错误等运行时错误,失败命令返回错误,其他命令仍然继续执行,已经执行成功的命令不会被 Redis 自动撤销。
因为 WATCH 不会锁住 key,也不会阻塞其他客户端写入。它只是记录被监视 key 的版本变化,如果这些 key 在 EXEC 前被改动,EXEC 就放弃执行。调用方认为冲突是少数情况,冲突发生后再重试,这正是乐观并发控制思想。
如果只是简单地批量顺序执行多个命令,Redis 事务足够;如果逻辑包含读取、判断、条件写入,并且希望减少客户端和服务端多次交互,Lua 更合适。但 Lua 脚本应保持短小,避免长时间阻塞 Redis,也不要误以为它提供自动回滚。
单纯 MULTI 和 EXEC 不一定够,因为业务通常需要先读库存再判断是否扣减。可以用 WATCH 监视库存 key,在 EXEC 前检测并发修改,失败后重试;也可以用 Lua 把判断和扣减放到服务端原子执行,通常 Lua 方案更直接。