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. 执行时的“运行时错误”(虽死犹进) 这才是真正的坑。
阅读全文