Skip to content

Redis - 事务

Redis 事务允许你将一组命令作为单个原子操作执行。这确保了没有其他客户端可以中断命令序列的执行。

  • 串行执行: 事务中的所有命令都是串行化并按顺序执行的。一旦触发 EXEC 命令,Redis 将不会响应其他任何客户端请求,直到事务完成。
  • 原子性(附带条件): 事务是原子性的。这意味着块中的所有命令要么全部执行,要么一个都不执行(在 EXEC 命令之前的语法错误情况下)。然而,Redis 不支持回滚。如果一个命令在运行时失败(例如,尝试对非整数值执行 INCR),事务将继续,并且所有之前成功的命令将保持提交状态。

事务由 MULTI 命令启动。在 MULTI 之后,客户端可以发送多个命令。这些命令不会立即执行,而是由 Redis 排队。事务最终由 EXEC 命令触发。

# 启动事务
127.0.0.1:6379> MULTI
OK
# 将设置用户名的命令排队。Redis 回复 QUEUED。
127.0.0.1:6379> SET user:1:name "Carol"
QUEUED
# 将递增用户登录计数的命令排队。
127.0.0.1:6379> INCR user:1:logins
QUEUED
# 执行事务。Redis 运行这两个命令并返回它们的结果。
127.0.0.1:6379> EXEC
1) OK
2) (integer) 1

WATCH 提供了一种检查并设置(CAS)机制。如果你 WATCH 一个键,只有当该键自从被监视以来未被其他客户端修改时,事务才会执行。如果键被修改,EXEC 命令将失败,返回 nil 回复,客户端可以重试操作。

假设两个客户端试图同时更新产品的库存。

# 初始状态:库存为 10
127.0.0.1:6379> SET product:123:inventory 10
OK
# --- 客户端 A 开始 ---
127.0.0.1:6379> WATCH product:123:inventory
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY product:123:inventory 2
QUEUED
# --- 在客户端 A 执行 EXEC 之前,客户端 B 快速购买 1 件商品 ---
# --- 客户端 B 的会话(立即执行)---
# 127.0.0.1:6379> DECR product:123:inventory
# (integer) 9
# --- 客户端 B 完成 ---
# --- 现在,客户端 A 尝试执行其事务 ---
127.0.0.1:6379> EXEC
(nil) # 事务失败,因为被监视的键已被更改。
# 客户端 A 的应用程序逻辑现在应该检测到 nil 回复,
# 重新获取库存,并重试整个 WATCH/MULTI/EXEC 序列。
CommandDescription
MULTI标记事务块的开始。后续命令将被排队。
EXEC执行在 MULTI 之后排队的所有命令。
DISCARD清除 MULTI 之后排队的所有命令,退出事务。
WATCH key [key …]监视给定的键。如果在 EXEC 之前有任何键被修改,事务将中止。
UNWATCH清除所有正在被监视的键。
  • 无运行时回滚: 请记住,如果像 INCR 这样的命令在 EXEC 期间对字符串值失败,事务中之前的命令(如 SET)不会回滚。你的应用程序必须处理这种可能性。
  • 语法错误: 如果命令存在语法错误(例如,将 SET 写成 SETT),现代 Redis 版本会在 EXEC 时中止整个事务。
  • 替代方案:Lua 脚本: 对于需要服务器端逻辑的复杂原子操作,Redis Lua 脚本通常是 WATCH/MULTI/EXEC 更强大、更灵活的替代方案。脚本总是原子性执行的。