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脚本将逻辑封装在服务端,减少了网络往返,性能通常更好。
相关推荐
2401_8654396318 小时前
CSS如何实现图片自动裁剪填充_巧用object-fit属性控制尺寸
jvm·数据库·python
m0_7488394918 小时前
HTML函数能否用液态金属散热提升性能_极端散热方案实测【汇总】
jvm·数据库·python
2301_8035389518 小时前
mysql添加索引导致插入变慢怎么办_索引优化与异步处理方案
jvm·数据库·python
2301_7826591818 小时前
如何防止SQL脏数据写入_利用触发器实现强一致性校验
jvm·数据库·python
我叫黑大帅18 小时前
Go 项目中 Redis 缓存的实用设计与实现(Cache-Aside 模式)
redis·后端·面试
2301_8176722618 小时前
如何实现元素从底部进入视口时触发 sticky 定位
jvm·数据库·python
InfinteJustice18 小时前
mysql如何排查插件加载失败原因_mysql plugin目录与权限核对
jvm·数据库·python
qq_1898070318 小时前
Go语言怎么连接Elasticsearch_Go语言Elasticsearch教程【收藏】
jvm·数据库·python
迷藏49418 小时前
**TiDB 在高并发场景下的性能优化实战:从慢查询到极致吞吐的跃迁**在现代分布式系统中,数据库不仅是数据存储的
java·数据库·python·性能优化·tidb
m0_6784854518 小时前
如何自动同步SQL异构表数据_利用触发器实现实时数据复制
jvm·数据库·python