bigywater.tech
Redis · 30

Redis 事务为什么没有回滚

对比两种错误——排队阶段就能查出的语法错误和只有运行时才暴露的类型错误,看Redis事务分别怎么处理,理解为什么它没有传统数据库那种回滚机制。

EXECABORT
排队期语法错误导致整个事务被丢弃
WRONGTYPE不影响其它命令
运行时错误只影响那一条命令自己
无运行时回滚
已执行的命令永远不会被撤销

做的事:故意制造两种不同性质的错误

一种是命令本身就不存在(排队阶段就能查出来),一种是命令合法但用错了类型(只有真的跑起来才知道)。

代码-- 场景A:排队时发一条根本不存在的命令 MULTI SET a 1 BOGUSCMD x SET b 2 EXEC -- 场景B:排队时全部合法,运行时才出错 MULTI SET counter 20 INCR counter LPUSH counter x -- counter是字符串,运行时才知道类型不对 SET another 1 EXEC

验证:场景A——一条命令名打错,整个事务被完全丢弃

EXEC都没有返回结果数组,直接是一个错误。

结果BOGUSCMD x → -ERR unknown command 'BOGUSCMD'... EXEC → -EXECABORT Transaction discarded because of previous errors. GET a → (nil) GET b → (nil)

验证:哪怕SET a 1和SET b 2这两条命令完全没问题,结果是a和b都没有被设置——因为排队阶段就已经发现有一条命令语法不对,Redis把整个事务标记为"损坏",EXEC直接拒绝执行,一条都不跑。

验证:场景B——排队时全部合法,运行时错误不影响其它命令

这才是真正"没有回滚"要说明的场景。

结果EXEC → [+OK, :21, -WRONGTYPE Operation against a key holding the wrong kind of value, +OK] GET counter → "21"(生效了) GET another → "1"(也生效了)
关键对比:场景A是"编译期错误"(排队阶段就能查出来的语法/命令名问题),Redis选择直接整体拒绝执行,这是它唯一会做的"类似回滚"的行为——但严格说这不叫回滚,因为它根本没有执行任何命令,无需回滚。场景B才是真正的"运行时错误"(命令语法对,参数类型运行时才知道不匹配),Redis的态度是:已经开始执行的命令,绝不会因为后面某条失败而撤销——SET counter 20和INCR counter的效果原封不动保留,LPUSH失败只是让EXEC返回的数组里对应位置是一个错误对象,SET another 1依然照常执行成功。Redis没有像关系型数据库那样的运行时回滚机制,官方的立场是:运行时错误通常意味着应用层的编程错误(比如对着字符串调用列表命令),这类错误理应在开发测试阶段就被发现,不值得为了这种情况让Redis事务背上性能更差的回滚机制。

分步演示:两种错误完全不同的处理方式

每一步都是本文上面对比两种错误跑出来的原始结果。

排队期错误整体丢弃,运行时错误从不回滚
这一步新验证的场景

    本文用到的工具

    • 本机 Redis 8.10.1
    • 裸socket + RESP协议:对比排队期语法错误和运行时类型错误两种场景
    京ICP备2026067502号-1京公网安备11010502063487号