什么是 Redis 事务?

你可以将 Redis 中的事务理解为:Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。

Redis 事务实际开发中使用的非常少,功能比较鸡肋,不要将其和我们平时理解的关系型数据库的事务混淆了。

除了不满足原子性和持久性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。明明一次批量执行多个命令就可以了,这种操作实在是看不懂。

因此,Redis 事务是不建议在日常开发中使用的

如何使用 Redis 事务

Redis 可以通过 MULTIEXECDISCARD 和 WATCH 等命令来实现事务(Transaction)功能。

MULTI 开启事务,EXEC 提交事务并执行,DISCORD 放弃事务,WATCH 监测一个或多个键的值在事务执行期间是否变化,若变化,则放弃执行。

> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
> GET PROJECT
QUEUED
> EXEC
1) OK
2) "JavaGuide"
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED
> GET PROJECT
QUEUED
> DISCARD
OK
# 客户端 1
> SET PROJECT "RustGuide"
OK
> WATCH PROJECT
OK
> MULTI
OK
> SET PROJECT "JavaGuide"
QUEUED

# 客户端 2
# 在客户端 1 执行 EXEC 命令提交事务之前修改 PROJECT 的值
> SET PROJECT "GoGuide"

# 客户端 1
# 修改失败,因为 PROJECT 的值被客户端2修改了
> EXEC
(nil)
> GET PROJECT
"GoGuide"

不过,如果 WATCH 与 事务 在同一个 Session 里,并且被 WATCH 监视的 Key 被修改的操作发生在事务内部,这个事务是可以被执行成功的(相关 issue:WATCH 命令碰到 MULTI 命令时的不同效果)。

事务内部修改 WATCH 监视的 Key:

> SET PROJECT "JavaGuide"
OK
> WATCH PROJECT
OK
> MULTI
OK
> SET PROJECT "JavaGuide1"
QUEUED
> SET PROJECT "JavaGuide2"
QUEUED
> SET PROJECT "JavaGuide3"
QUEUED
> EXEC
1) OK
2) OK
3) OK
127.0.0.1:6379> GET PROJECT
"JavaGuide3"

事务外部修改 WATCH 监视的 Key:

> SET PROJECT "JavaGuide"
OK
> WATCH PROJECT
OK
> SET PROJECT "JavaGuide2"
OK
> MULTI
OK
> GET USER
QUEUED
> EXEC
(nil)

Redis 官网相关介绍 https://redis.io/topics/transactions 如下:

Redis 事务

Redis 事务机制

  1. 在 EXEC 执行前,命令都放到队列中缓存,输入可以支持的命令返回 QUEUED,输入不支持的命令返回 error。
  2. 收到 EXEC 后进入执行,若命令错误,则无法执行;反之执行命令,当命令执行时报错,其余命令继续执行。

Redis 事务支持原子性吗

  1. 若操作命令本身有问题,入队前 Redis 就会判断,不会执行任何指令,原子性。
  2. 但若执行中出问题,如操作与数据结构不匹配,则该指令不执行,后续操作执行。
  3. 执行 EXEC 命令时,Redis 实例故障,事务执行失败。若有 AOF 日志,可以通过 redis-check-aof 来检查,将未完成的事务操作从 AOF 中去除。但如果没有开启,则无法恢复数据,更谈不上原子性。

因此,Redis 事务其实是不满足原子性的。

Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。

相关 issue :

Redis 事务支持持久化吗

Redis 支持持久化,但是 Redis 事务的持久性也是没办法保证的。

Redis 事务支持一致性吗

  1. 入队出错+执行出错,错误指令没执行,具有一致性。
  2. 实例故障,事务执行期间不进行 RDB,AOF 可以清楚未执行事务,具有一致性。

Redis 事务支持隔离性吗

  1. 并发在 EXEC 命令前执行,需要使用 WATCH 机制来实现。
  2. 并发在 EXEC 命令后执行,隔离性可以保证。

Redis 事务有什么缺陷

除了不满足原子性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。明明一次批量执行多个命令就可以了,这种操作实在是看不懂。

如何解决 Redis 事务的缺陷

Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常类似。我们可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。

一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。

不过,如果 Lua 脚本运行时出错并中途结束,出错之后的命令是不会被执行的。并且,出错之前执行的命令是无法被撤销的,无法实现类似关系型数据库执行失败可以回滚的那种原子性效果。因此, 严格来说的话,通过 Lua 脚本来批量执行 Redis 命令实际也是不完全满足原子性的。

如果想要让 Lua 脚本中的命令全部执行,必须保证语句语法和命令都是对的。

另外,Redis 7.0 新增了 Redis functions 特性,你可以将 Redis functions 看作是比 Lua 更强大的脚本。