做Redis有一段时间之后,你会发现一个特别有意思的现象:只要一聊到事务,很多人第一反应是“Redis的事务和MySQL不一样”,但要真问你哪里不一样,大家又支支吾吾,只会说“Redis事务不太靠谱”。这篇内容,我就把Redis事务里那个最容易被误解的“弱化原子性”掰开揉碎讲清楚,包括MULTI、EXEC、WATCH的完整用法,以及为什么Redis官方文档里写得明明白白“事务执行过程中某条命令出错,其他命令照样执行,不保证回滚”。
这个内容并不新鲜,但它属于那种“面试天天问、开发天天踩、文档天天看、理解天天错”的典型知识点。对刚接触Redis的读者,这篇可以帮你把事务的边界和底线一次弄明白;对已经用过一段时间的开发者,这篇里整理的一些排查思路和踩坑记录,可能比你自己翻文档更省时间。
1. Redis事务的本质:先排队,后执行
1.1 事务能力从哪里来
Redis的事务模型其实非常朴素,核心就四个命令:MULTI、EXEC、DISCARD、WATCH。你执行MULTI之后,Redis会把你后续发来的命令全部放进一个队列,等EXEC一发,服务器按顺序把队列里的命令一口气执行完,期间不插队、不打断,这就是Redis事务最基础的语义。
这个机制的关键词是“按顺序”和“不被打断”。它和关系型数据库“要么都成功、要么都失败”的ACID事务模型有着本质区别。Redis事务保证的是两件事:一是事务中的命令会被原子性地隔离执行,不会在执行到一半时插入其他客户端的命令;二是只要EXEC被调用,队列里的命令就会全部执行完,不会因为排队阶段的问题中途放弃。
但很多人没注意到的是,这个“原子性”指的是执行过程的原子性,而不是结果层面的原子性。这么说有点绕,我举个例子方便理解。
可以把Redis事务想象成你去食堂打饭:你拿着托盘排队,食堂师傅按顺序给你盛菜。这个过程中别人插不了队,这叫“隔离性”;但师傅给你盛的菜好不好吃、合不合你胃口,那是另一回事。Redis事务也一样——它保证“命令一定会被执行”,但不保证“命令执行后结果是正确的”。
1.2 两种失败,待遇完全不同
Redis事务之所以让人困惑,是因为它有两种不同的失败场景,处理方式天差地别。
第一种是入队失败。比如你往队列里塞了一条语法错误、参数个数不对的命令,Redis在MULTI阶段就能发现,直接返回错误,并且整个事务会被标记为无效。这种情况下,你最终执行EXEC时会直接得到一个EXECABORT错误,事务里所有命令都不会执行。这类错误可以理解成“代码编译期错误”。
第二种是执行失败。比如你的命令语法没问题,但执行时触发了运行时错误——最典型的就是对一个字符串类型的key执行LPUSH操作。这种错误在入队阶段根本发现不了,Redis会在EXEC执行到那条命令时才报错。诡异的是,它报错之后并不会停下来,而是继续执行后面排队的命令,而且之前已经执行成功的命令也不会回滚。
这就是标题里说的“弱化的原子性”的核心:Redis事务出现运行时错误时,不做回滚、不中断执行、前功不能尽弃。你要是拿MySQL那套事务预期去套Redis,很容易出事。
1.3 DISCARD和WATCH分别承担什么角色
DISCARD比较好理解,就是在MULTI之后、EXEC之前,中止当前事务,清空命令队列,回到普通模式。它可以理解成“这顿饭我不打了,托盘放回去”。
WATCH稍微复杂一点,它是Redis里唯一一个能让你对事务做“条件控制”的机制。它的原理是乐观锁:你可以在MULTI之前WATCH一个或多个key,如果在执行EXEC之前,这些key中的任何一个被其他客户端修改了,那EXEC就会返回nil,表示事务执行失败——但注意,不是回滚,而是“我拒绝执行刚才排队的那批命令”。
后面我在实操部分会详细演示WATCH的用法。先记住结论:WATCH才是Redis事务里真正有用的“条件保障”手段,但它的工作方式和传统事务完全不在一个维度上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
