Redis事务

Redis事务

引言

我们通常理解的"事务",简单来说就是一句话:要么一起成功,要么一起失败,绝不能出现"只完成一半"的情况。

这就好比一场资金转账。假设用户A要给用户B转账5块钱。在程序里,这通常包含两个动作:

  1. 扣款:从用户A的账户里扣除5块钱。
  2. 入账:给用户B的账户增加5块钱。

在现实世界里,这两个动作必须是铁板一块的。如果扣款成功了,但入账失败了(比如网络突然断了),那这5块钱就凭空消失了,这在金融系统里是绝对无法容忍的灾难。反之,如果扣款失败了,入账却成功了,那银行就亏大了。 所以,我们需要一种机制,把这两个动作打包成一个"不可分割"的整体。这就是原子性(Atomicity)。

Redis执行事务的命令

第一步:WATCH

行动开始前,我们需要确保目标数据是"安全"的。WATCH 命令就是我们的"盯梢员"。

在执行事务之前,我们先用 WATCH key [key ...] 监视那些关键的键(比如用户的余额)。这就好比在转账前,你死死盯着账户余额的数字。如果在行动结束前,这个数字被其他人改动了,那我们的行动就会立刻中止,以此来避免基于过期数据做出错误的决策。

第二步:MULTI

一旦确认环境"安全",我们就可以开始收集指令了。MULTI 命令标志着事务的正式开始。

当你输入 MULTI 后,Redis会进入"排队模式"。接下来你输入的所有命令(比如扣款、入账),都不会立即执行,而是被放入一个队列中静静等待。这一步确保了我们的操作序列是完整的,不会被中间插队。

第三步:EXEC

当所有命令都准备就绪,就是发起总攻的时刻。EXEC 命令会触发队列中所有命令的执行。

Redis会一口气、按顺序地把队列里的命令全部执行完。更重要的是,它会先回头检查一下 WATCH 盯着的那些键:如果它们没变,命令就顺利执行;如果它们变了,Redis会直接放弃整个队列的操作(返回nil),绝不允许出现"半成品"的脏数据。

补充动作:解散与重置

  • DISCARD:如果你在 MULTI 之后突然不想干了,可以用 DISCARD 来清空队列,让Redis恢复到正常状态。
  • UNWATCH:如果你不想再监视某些键了,或者事务执行完毕(EXECDISCARD 后会自动取消监视),可以用 UNWATCH 来取消监视,释放资源。

"伪"原子性

在 MySQL 等传统关系型数据库中,原子性意味着"全有或全无"(All or Nothing)。如果中间出错,系统会自动执行回滚(Rollback),把数据恢复到最初的状态。

然而,Redis 事务的哲学截然不同。它奉行"简单至上 ",不支持传统意义上的回滚机制。这主要体现在两个方面:

  1. 不回滚业务错误

    如果事务中的某条命令执行失败(比如对一个字符串类型的键执行 INCR),Redis 不会撤销之前已经执行成功的命令。例如,A扣款成功了,但给B入账时命令写错了,Redis不会把A的钱还回去。它只会记录这个错误并继续执行后续命令(如果有的话)。

  2. 靠"放弃"来保证一致性

    既然不支持回滚,那怎么保证数据不出错呢?答案就是 WATCH。

    Redis 的策略是:与其出错后费力地回滚,不如在出错前直接放弃。

    通过 WATCH 机制,Redis 能够检测到数据是否被并发修改。如果检测到冲突,它不会去尝试修正数据,而是直接放弃整个事务(返回 nil)。这就把处理问题的责任推给了客户端------客户端收到失败信号后,应该重新尝试整个操作。

为什么没有回滚?

你可能会问,为什么不实现像MySQL那样的回滚机制呢?官方的解释是:Redis追求简单和极致的性能。回滚机制需要记录undo log(回滚日志),这会带来额外的开销和复杂性。对于Redis来说,大多数命令都是简单的写操作,且数据通常作为缓存存在,即使出错,也可以通过从后端数据库重新加载来恢复。因此,Redis选择了一种更轻量级的方案。

与Lua脚本的抉择

Redis还支持通过EVAL命令执行Lua脚本。Lua脚本在Redis中也是原子执行的,并且它支持真正的条件判断和错误处理。在很多场景下,Lua脚本可以替代Redis事务,甚至比事务更强大、更简洁。

那么,何时使用事务,何时使用Lua脚本呢?

  • 使用Redis事务:当你需要执行的是一组简单的、固定的Redis命令,且业务逻辑相对简单时。
  • 使用Lua脚本:当你需要复杂的逻辑判断、循环、或者需要在服务端进行复杂的计算时。Lua脚本将逻辑封装在服务端,减少了网络往返,性能通常更好。
相关推荐
yurenpai(27届找实习中)7 小时前
redis_点评(21.好友关注——关注、取关功能实现;共同关注功能实现)
数据库·redis·缓存
Rick19937 小时前
索引的排序和分组
数据库·mysql
爱莉希雅&&&8 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控
JohnYan8 小时前
工作笔记 - PG分组极值
数据库·后端·postgresql
清溪5498 小时前
DataEase H2 JDBC-RCE(CVE-2025-32966)复现
数据库·安全
ServBay8 小时前
不要再盲选了,PostgreSQL、MySQL与SQLite真实性能对比
数据库·mysql·sqlite
Trouvaille ~8 小时前
【Redis篇】Set 与 Zset:集合运算与排行榜的终极武器
数据库·redis·缓存·set·跳表·后端开发·zset
無限進步D8 小时前
MySQL 创建和管理表
数据库·mysql
六月雨滴8 小时前
归档模式配置与切换
数据库·oracle·dba
卡次卡次18 小时前
vibecoding起步注意点:插件、Skills、MCP、Hooks
服务器·数据库·python·oracle