Redis 事务那些事儿:实用技巧和避坑指南!

一、为什么我们关心Redis事务?

在Java开发的日常工作中,Redis几乎无处不在。你可能用它做缓存、排行榜、分布式锁,甚至用它做轻量级的数据存储。

但随着业务复杂度提升,很多人都会遇到这样的问题:

多个Redis操作需要保证原子性,怎么做?Redis的事务和MySQL事务一样靠谱吗?WATCH、MULTI、EXEC这些命令到底怎么用?能不能防止并发下的数据不一致?

这些问题看似简单,实则暗藏不少坑。今天这篇文章,我想用最实在的语言,把Redis事务的本质、用法和注意事项讲清楚,帮你在实际开发中少踩坑。

二、Redis事务机制全解析

1. Redis到底支不支持事务?

结论先行:Redis支持事务,但和MySQL事务完全不是一回事。

MySQL事务强调ACID(原子性、一致性、隔离性、持久性),而Redis的事务机制更像是“命令打包、顺序执行”,没有复杂的隔离和回滚机制。

2. Redis事务的基本命令和用法

Redis事务的核心命令有四个:MULTI、EXEC、DISCARD、WATCH。

(1) MULTI/EXEC:事务的开始与提交

MULTI:开启事务,后续命令进入队列EXEC:提交事务,队列中的命令依次执行

举个例子:

复制
# 1. 初始化库存 127.0.0.1:6379> set a:stock 100 OK 127.0.0.1:6379> set b:stock 200 OK # 2. 开启事务 127.0.0.1:6379> multi OK # 3. 将a:stock减1 127.0.0.1:6379> decr a:stock QUEUED # 4. 将b:stock减1 127.0.0.1:6379> decr b:stock QUEUED # 5. 实际执行事务 127.0.0.1:6379> exec 1) (integer) 99 2) (integer) 199 127.0.0.1:6379>1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.

(2) DISCARD:放弃事务

如果在MULTI之后,发现有问题,可以用DISCARD放弃事务,清空命令队列。

(3) WATCH:乐观锁的实现

在并发场景下,单靠MULTI/EXEC还不够。比如转账操作,两个客户端同时读取余额,都判断可以转账,结果都扣了钱,余额就出错了。

这时候可以用WATCH命令,类似乐观锁。WATCH会监控指定的key,如果在事务提交前这些key被其他客户端修改,EXEC会失败,事务不会执行。

示例:

复制
127.0.0.1:6379> get a:stock "99" 127.0.0.1:6379> watch a:stock OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decr a:stock QUEUED 127.0.0.1:6379> decr b:stock QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379>1.2.3.4.5.6.7.8.9.10.11.12.13.

如上所示,a:stock在EXEC前被其他客户端修改,EXEC会返回null,表示事务失败。

三、Redis事务的常见“坑”和注意事项

1. 没有回滚机制

只要EXEC执行,前面的命令就算后面有错,也不会回滚。比如:

复制
127.0.0.1:6379> multi OK 127.0.0.1:6379> set name tom QUEUED 127.0.0.1:6379> incr name QUEUED 127.0.0.1:6379> set age 18 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range 3) OK 127.0.0.1:6379>1.2.3.4.5.6.7.8.9.10.11.12.13.
set name tom执行成功。incr name 执行时报错(因为 name 是字符串,不能自增)。set age 18依然会被执行。

注意:Redis事务中,某条命令出错不会影响其他命令的执行,也不会回滚。

那如果我们想实现回滚的效果怎么办呢?

2. 如何用DISCARD修复?

如果你在MULTI之后发现命令写错了,可以在EXEC之前执行DISCARD,这样所有已入队的命令都不会被执行,数据不会被修改。

复制
127.0.0.1:6379> multi OK 127.0.0.1:6379> set addr bj QUEUED 127.0.0.1:6379> incr addr QUEUED 127.0.0.1:6379> set code 110 QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get addr (nil) 127.0.0.1:6379>1.2.3.4.5.6.7.8.9.10.11.12.13.

执行结果:

set addr bj、set code 110都不会被执行。事务被彻底放弃,Redis状态不会有任何变化。

注意点:

一旦执行了EXEC,就无法再用DISCARD撤销事务,已经执行的命令不会回滚。DISCARD只能在事务提交前使用,相当于“撤销”本次事务。3. 没有隔离性

Redis事务期间,其他客户端依然可以操作相关key。WATCH只能监控key本身的变化,不能保证更复杂的业务一致性。

比如你WATCH了a:stock,但b:stock被其他客户端修改了,你的事务依然会执行。

四、实用干货:Redis事务的正确打开方式

(1) 能用原子命令就用原子命令

Redis本身很多命令就是原子的,比如INCR、DECR、SETNX等,优先用这些。

(2) 事务只保证命令的“批量、顺序、一次性”执行

不保证命令之间的隔离和回滚。

(3) WATCH适合乐观锁场景

比如扣库存、转账等,先WATCH关键key,判断条件后再MULTI/EXEC。

(4) 复杂业务建议用Lua脚本

Lua脚本在Redis中是原子执行的,可以实现更复杂的业务逻辑和回滚。

(5) 不要把Redis事务当成数据库事务用

Redis事务和MySQL事务完全不是一回事,不能指望它帮你兜底所有一致性问题。

五、Redis事务和ACID的对比

很多同学会问:Redis事务到底支持ACID的哪几项?

(1) 原子性(Atomicity)

Redis事务只保证“命令队列”整体的原子性,不保证单条命令的原子性。EXEC时,要么所有命令都执行,要么都不执行(WATCH监控失败时)。

(2) 一致性(Consistency)

Redis事务本身不保证数据的一致性,需要开发者自己保证。

(3) 隔离性(Isolation)

Redis事务没有严格的隔离性,事务执行期间,其他客户端可以修改相关key。

(4) 持久性(Durability)

取决于Redis的持久化配置(RDB、AOF),和事务机制本身无关。

一句话总结:Redis事务只保证“命令批量执行的原子性”,不保证隔离和回滚。

六、面试高频问答

(1) Redis事务和MySQL事务的区别?

MySQL事务支持ACID,Redis事务只保证命令批量执行的原子性。MySQL事务有回滚机制,Redis事务没有。MySQL事务有隔离级别,Redis事务没有。

(2) Redis事务失败会回滚吗?

不会。只要EXEC执行,前面的命令就算后面有错,也不会回滚。

(3) WATCH命令的作用是什么?

实现乐观锁,监控指定key,防止并发下的数据不一致。

(4) Redis事务适合哪些场景?

适合批量命令、简单乐观锁场景。不适合强一致性、复杂回滚的业务。

阅读剩余
THE END