Redis事务原子性迷思,Lua脚本是明智之选?
摘要:作为一个长期和关系型数据库(RDBMS)打交道的开发者,初次查阅 Redis 文档时,看到 MULTI、EXEC、DISCARD 这些指令,心中难免涌起一股由于熟悉而带来的安全感。
我们的大脑会自动建立映射:MULTI 就是
写在前面的话
作为一个长期和关系型数据库(RDBMS)打交道的开发者,初次查阅 Redis 文档时,看到 MULTI、EXEC、DISCARD 这些指令,心中难免涌起一股由于熟悉而带来的安全感。
我们的大脑会自动建立映射:MULTI 就是 BEGIN,EXEC 就是 COMMIT,DISCARD 就是 ROLLBACK。这套组合拳打下来,所有的业务逻辑似乎都应该具备了“不成功便成仁”的原子性保障。
但这恰恰是 Redis 给我上的第一课:相似的命名背后,往往藏着截然不同的灵魂。 当你把 MySQL 的事务观生搬硬套到 Redis 身上时,错付就已经开始了。
这篇文章将带你剥开 Redis 事务的外衣,从“原子性”的定义偏差说起,聊聊为什么在现代开发中,我们越来越倾向于用 Lua 脚本来替代它。
一、先把误会解开:Redis 事务不是 ACID
在关系型数据库的世界里,“事务”二字重若千钧,它几乎等同于 ACID(原子性、一致性、隔离性、持久性)。我们习惯了“要么全有,要么全无”的安全感。
而在 Redis 的世界里,MULTI 和 EXEC 更像是一个批处理信号:
把一堆命令先放进队列里排队,等到 EXEC 时,一次性、按顺序地执行它们。
这里有一个巨大的认知偏差。当我们谈论 Redis 的“原子性”时,Redis 指的其实是 隔离性(Isolation),而不是 回滚(Rollback)。
它保证的是:我执行这段命令的时候,别人不能插队(独占执行)。
它不保证的是:如果我执行到一半报错了,我会帮你把前面的操作撤销(失败回滚)。
为了更直观地理解,我们可以对比一下 Redis 事务和标准 ACID 事务的区别:
特性
关系型数据库 (MySQL)
Redis 事务
差异解读
原子性 (Atomicity)
All or Nothing
失败即回滚,如同未发生过
All or Partial
没得商量,错了就错了,剩下的接着干
Redis 不支持 Rollback,部分成功是常态
一致性 (Consistency)
强一致性
约束必须满足
弱一致性
依赖业务代码保障
Redis 不会校验业务约束(如外键、非空等)
隔离性 (Isolation)
有多种隔离级别 (RC/RR/Serializable)
串行化执行
执行期间不可被打断
得益于单线程模型,EXEC 期间天然隔离
持久性 (Durability)
WAL 日志保障
掉电不丢失
取决于 AOF/RDB 配置
默认配置下通常有数据丢失风险
一句话总结:
Redis 事务是“命令队列 + 独占执行”,绝不是“失败回滚 + 强一致”。
二、残酷的真相:它真的不包回滚
为了把这个概念刻进 DNA,我们看两种真实的错误场景。
1. 入队时的“低级错误”(全员连坐)
如果你在命令入队阶段就犯了语法错误(比如参数写少了),Redis 还是讲道理的,它会直接拒绝整个事务。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key1 value1
QUEUED
127.0.0.1:6379> SET key2 # <--- 语法错误:少了参数
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
这时候,所有命令都不会执行。这符合我们对“事务”的预期。
2. 执行时的“运行时错误”(虽死犹进)
这才是真正的坑。
