Redis 事务机制

1993 字
4 分钟阅读

在数据库领域,“事务”(Transaction)是一个可靠的词,它通常与ACID(原子性、一致性、隔离性、持久性)四大特性紧密绑定,是保证数据绝对可靠的基石。然而,当我们谈论Redis的事务时,会发现它是一个非常“另类”的存在。

Redis的事务同样允许我们将一组命令打包执行,但它所做的承诺,与我们熟知的关系型数据库事务大相径庭。

是什么:Redis事务的本质

Redis事务的本质,是一个命令的集合。它允许客户端将多个命令打包,然后一次性、按顺序地执行,期间不会被其他客户端的命令打扰。

简单来说,Redis事务的核心就是 “一组命令,序列化,不许加塞”

它主要围绕四个核心命令来工作:

  • MULTI:开启一个事务块。此命令之后的所有命令都会被放入一个队列中,而不会立即执行。
  • EXEC:执行事务队列中的所有命令。
  • DISCARD:放弃事务。清空事务队列,并退出事务状态。
  • WATCH:一个乐观锁,用于在事务执行前,监视一个或多个key。如果在EXEC执行前,被监视的key被其他客户端修改了,那么整个事务将被取消。

能干嘛:Redis事务的核心保证

Redis事务提供了一个在队列中一次性、顺序性、排他性地执行一系列命令的能力。

想象一下去银行办理业务,你需要在柜台连续办理“查询余额”、“取款”、“转账”三个操作。

  • 没有事务:你每办完一个业务,就可能被插队,需要重新排队,这三个操作可能会被其他人的业务打断。
  • 使用Redis事务:你相当于告诉柜员:“接下来我要办三个业务,请不要接待其他人,直到我全部办完。” 柜员会把你的三个请求单(命令)收好,然后关上窗口,一件一件为你办完,再打开窗口接待下一个人。

怎么玩:Redis事务三部曲

一个典型的Redis事务流程分为三个阶段:

  1. 开启事务 (MULTI)

    • 客户端发送 MULTI 命令。
    • 服务器返回 OK,并进入事务状态。
    • 此后,客户端发送的所有命令(如 SET, INCR 等)都会被服务器接收并放入一个先进先出的事务队列中,服务器会返回 QUEUED,表示命令已入队,但并未执行
  2. 命令入队

    • 客户端继续发送多个命令。
    • 服务器逐一将这些命令存入事务队列。
  3. 执行或放弃事务 (EXEC / DISCARD)

    • 执行 (EXEC) :客户端发送 EXEC 命令。Redis会原子地、顺序地执行事务队列中的所有命令。所有命令执行完毕后,会将所有结果一次性返回给客户端。
    • 放弃 (DISCARD) :如果客户端在 EXEC 之前发送 DISCARD 命令,服务器会清空整个事务队列,然后退出事务状态,什么都不会发生。

示例:

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET balance 1000
QUEUED
127.0.0.1:6379> DECRBY balance 200
QUEUED
127.0.0.1:6379> INCRBY transfer_count 1
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 800
3) (integer) 1

Redis事务 vs. 数据库事务:截然不同的哲学

这是理解Redis事务最关键的部分。我们不能用看待MySQL事务的眼光去看待Redis事务。

image

  1. 不保证原子性 (Atomicity)

    • 数据库事务:严格保证原子性。事务中的所有操作,要么全部成功,要么全部失败回滚。
    • Redis事务不保证原子性,不支持回滚。如果一个事务队列中的某个命令在执行时出错(例如,对一个字符串类型的key执行列表操作),只有这个出错的命令会失败,而其他正确的命令会继续执行。
    • 为什么这么设计? Redis的作者认为,命令执行失败通常是编程错误(如语法错误)导致的,这类问题应该在开发阶段就被发现,而不是通过运行时的回滚机制来解决。Redis追求的是极致的简单和高性能,回滚机制会使内部逻辑复杂化。
  2. 独特的隔离性 (Isolation)

    • 数据库事务:有多种隔离级别(读未提交、读已提交、可重复读、串行化)来处理复杂的并发读写问题。

    • Redis事务没有隔离级别的概念。但它通过另一种方式保证了隔离:

      • 排他性执行:由于Redis是单线程执行命令的,当一个事务通过 EXEC 开始执行时,它会独占整个线程,直到队列中的所有命令都执行完毕。在此期间,任何其他客户端的命令都无法插入
      • 执行前不可见:在 EXEC 执行之前,所有入队的命令都只是存在队列里,没有被实际执行。因此,不存在“事务内的查询要看到事务里的更新,而事务外的查询不能看到”这种复杂的隔离问题。

小结

Redis事务的设计,是一种典型的权衡与取舍,它舍弃了关系型数据库那种重量级的、绝对可靠的事务模型,换取了极致的简单和高性能。

  • 它保证了什么?

    • 批量操作的原子性:从发送 EXEC 到执行结束,这一整个“执行批量命令”的动作是原子的,不会被中断。
    • 顺序性:所有命令会严格按照入队的顺序执行。
    • 排他性:事务执行期间,不会有其他命令来“插队”。
  • 它不保证什么?

    • 命令级别的原子性:不保证队列中的所有命令都同时成功或失败。
    • 回滚能力:没有事务执行到一半进行回滚的能力。

因此,在使用Redis事务时,我们应将其理解为一个 “批量命令执行器” ,它能确保一组操作被连续、不间-断地执行。这在很多场景下已经足够,例如需要一次性更新多个相关key,而又不希望被其他操作打断的场景。对于需要强原子性保证的业务,则可能需要通过Lua脚本或在客户端层面实现更复杂的逻辑。

相关文章

最后更新:2025年09月29日
分享: