Redis事务相关

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

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

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

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

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


二、Redis事务机制全解析

2.1 Redis到底支不支持事务?

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

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

2.2 Redis事务的基本命令和用法

Redis事务的核心命令有四个:MULTIEXECDISCARDWATCH

2.2.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>

2.2.2 DISCARD:放弃事务

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

2.2.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>

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


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

3.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>
  • set name tom执行成功。
  • incr name 执行时报错(因为 name 是字符串,不能自增)。
  • set age 18依然会被执行。

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

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

3.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>

执行结果:

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

注意点:

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

3.3 没有隔离性

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

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


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

  1. 能用原子命令就用原子命令
    Redis本身很多命令就是原子的,比如INCRDECRSETNX等,优先用这些。

  2. 事务只保证命令的“批量、顺序、一次性”执行
    不保证命令之间的隔离和回滚。

  3. WATCH适合乐观锁场景
    比如扣库存、转账等,先WATCH关键key,判断条件后再MULTI/EXEC。

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

  5. 不要把Redis事务当成数据库事务用
    Redis事务和MySQL事务完全不是一回事,不能指望它帮你兜底所有一致性问题。


五、Redis事务和ACID的对比

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

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

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

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

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

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


六、面试高频问答

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

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

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

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

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

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

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

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

posted @ 2025-05-25 21:03  IT6889  阅读(32)  评论(0)    收藏  举报