Redis事务学习笔记

Redis事务学习笔记

文章目录

  • 前言
  • 一、先搞懂:Redis事务到底是什么?
  • 二、四大核心命令 课堂实操案例
    • 1. MULTI + EXEC 正常提交事务
    • 2. DISCARD 放弃事务,清空队列
    • 3. WATCH 乐观锁,解决并发数据冲突
  • 三、致命大坑:Redis事务**不支持自动回滚**
    • 场景1:组队阶段语法错误 → 整个事务全部作废
    • 场景2:组队无错误,执行时报错 → 正确命令正常生效,不会回滚
    • 官方不做回滚的设计原因(面试标准答案)
  • 四、Redis事务 VS MySQL事务 ACID对比
    • Redis事务独有两个特性
  • 五、Redis事务适用&不适用场景
    • 适合使用
    • 不推荐使用
  • 六、期末/面试高频简答题汇总
  • 结尾小结

前言

Redis事务不同于MySQL事务,接下来,请跟随我的脚步,一起来学习吧


提示:以下是本篇文章正文内容,下面案例可供参考

一、先搞懂:Redis事务到底是什么?

Redis事务和MySQL事务初衷相似:把多条Redis命令打包成一组操作,执行过程中不会被其他客户端请求插队打断。
但两者本质差别巨大:MySQL完整支持ACID四大特性,Redis只是简易事务,原子性残缺、没有隔离级别、出错不会自动回滚

Redis事务分为两大阶段:

  1. 组队阶段(入队):执行MULTI开启事务,后续输入的所有命令不会立刻执行,全部存入事务队列,控制台返回QUEUED排队标识;中途不想提交可以用DISCARD清空整个队列,直接放弃本次事务。
  2. 执行阶段(批量运行):输入EXEC提交事务,Redis会单线程串行执行队列里所有指令,执行期间阻塞其他客户端请求,保证命令不会穿插执行。

二、四大核心命令 课堂实操案例

1. MULTI + EXEC 正常提交事务

流程:开启事务→批量写入命令→提交统一执行

127.0.0.1> MULTI # 开启事务 OK 127.0.0.1(TX)> set username eric QUEUED 127.0.0.1(TX)> set age 20 QUEUED 127.0.0.1(TX)> get username QUEUED 127.0.0.1(TX)> EXEC # 提交,批量执行所有排队命令 1) OK 2) OK 3) "eric"

关键点:组队阶段无法查询数据,只有执行EXEC后,数据修改才会真正生效。

2. DISCARD 放弃事务,清空队列

写了一半发现逻辑错误,不想提交,直接丢弃所有排队指令,不会产生任何数据变更:

127.0.0.1> MULTI OK 127.0.0.1(TX)> set k1 v1 QUEUED 127.0.0.1(TX)> DISCARD # 放弃事务 OK 127.0.0.1> get k1 (nil)

3. WATCH 乐观锁,解决并发数据冲突

这是Redis事务最核心的拓展命令,也是面试必考题。
作用:事务开启前监控一个或多个key,如果在EXEC执行前,这些key被其他客户端修改,本次事务直接作废,所有命令都不会执行。

模拟场景:账户余额100,两个客户端同时发起扣款操作
客户端1操作:

127.0.0.1> set balance 100 OK 127.0.0.1> WATCH balance # 监控余额key OK 127.0.0.1> MULTI OK 127.0.0.1(TX)> decrby balance 80 QUEUED # 切到客户端执行扣款

客户端2并发操作:

127.0.0.1> decrby balance 50 (integer) 50

切回客户端1执行提交:

127.0.0.1(TX)> EXEC (nil) # 返回nil,事务失效,防止超扣

原理和数据库乐观锁版本号思路一致,完美解决并发修改覆盖问题。

三、致命大坑:Redis事务不支持自动回滚

90%初学者都会踩这个雷,分两种报错场景区分,期末必考:

场景1:组队阶段语法错误 → 整个事务全部作废

输入命令格式非法,入队时直接报错,后续执行EXEC会直接丢弃所有指令,一条都不执行。

127.0.0.1> MULTI OK 127.0.0.1(TX)> set k1 # 缺少value,语法错误,入队报错 (error) ERR wrong number of arguments for 'set' 127.0.0.1(TX)> set k2 v2 QUEUED 127.0.0.1(TX)> EXEC (error) EXECABORT Transaction discarded because of previous errors. # k1、k2均无数据

场景2:组队无错误,执行时报错 → 正确命令正常生效,不会回滚

命令语法没问题,但操作数据类型不匹配,执行失败时,其他正常执行的命令不会撤销。

127.0.0.1> MULTI OK 127.0.0.1(TX)> set num 100 # 正常指令 QUEUED 127.0.0.1(TX)> incr numstr # numstr不存在,无法自增,执行报错 QUEUED 127.0.0.1(TX)> set name eric # 正常指令 QUEUED 127.0.0.1(TX)> EXEC 1) OK 2) (error) ERR value is not an integer or out of range 3) OK

执行完成后,num=100name=eric真实存入Redis,报错语句直接跳过,没有任何回滚操作。

官方不做回滚的设计原因(面试标准答案)

  1. Redis核心定位是高性能缓存,回滚机制会增加底层代码复杂度,大幅降低执行效率;
  2. 语法、数据类型错误都属于开发阶段bug,上线前单元测试就能提前规避,不需要线上兜底回滚;
  3. 如果需要完整原子执行、出错全部回滚的能力,优先使用Lua脚本。

四、Redis事务 VS MySQL事务 ACID对比

很多面试官会提问:Redis事务满足标准ACID吗?一张表讲清差异:

四大特性MySQL事务Redis MULTI事务
原子性(A)完全满足,要么全部成功,失败全部回滚残缺,执行过程不插队,但单条命令出错不会回滚其他操作
一致性©内置约束保障,异常自动修复数据合法无内置机制,一致性需要业务代码自行维护
隔离性(I)提供四大隔离级别,读写互不干扰无隔离级别概念,事务未提交的数据全局可见
持久性(D)提交后数据永久落盘和事务无关,由RDB/AOF持久化配置控制

Redis事务独有两个特性

  1. 串行执行:EXEC运行期间,Redis单线程只处理当前事务队列,外部请求全部阻塞;
  2. 无中间态:组队阶段的修改不会对外可见,只有提交后才生效。

五、Redis事务适用&不适用场景

适合使用

  1. 批量缓存写入,多条命令需要连续执行、不被其他请求打断;
  2. 简单并发控制:库存扣减、账户余额变更,搭配WATCH乐观锁;
  3. 轻量统计类批量操作,对数据一致性要求不高。

不推荐使用

  1. 金融核心业务(转账、订单支付),零容忍数据错乱;
  2. Redis集群环境,多键分属不同slot时,MULTI无法批量操作;
  3. 复杂多分支业务逻辑,需要完整原子回滚;

补充:需要强原子性统一用Lua脚本,脚本执行全程不可中断,出错不会半写入数据。

六、期末/面试高频简答题汇总

  1. Redis事务分为哪两个阶段?
    答:组队阶段(MULTI入队,命令标记QUEUED)、执行阶段(EXEC批量执行),DISCARD命令可以直接丢弃未提交的事务队列。

  2. WATCH命令的作用是什么?
    答:实现Redis乐观锁,监控指定key;若事务提交前key被外部客户端修改,EXEC会返回nil,整个事务作废。

  3. Redis事务为什么不支持回滚?
    答:Redis主打高性能,回滚会增加底层复杂度;语法、类型错误属于开发bug,测试阶段可提前发现;需要完整原子操作推荐Lua脚本。

  4. 两种事务报错处理有什么区别?
    答:①入队时语法错误:整个事务直接作废;②组队无错、执行时报错:正常命令正常保存,错误语句跳过,不会回滚。

  5. Redis事务完整支持ACID吗?
    答:不支持。原子性残缺、无隔离级别,一致性需要业务代码维护,持久性由持久化配置决定,和事务本身无关。

结尾小结

之前一直用MySQL事务的固有思维写Redis代码,踩了不少坑,现在彻底分清两者的区别:Redis事务只是命令批量打包工具,不是传统关系型数据库的标准事务。
日常简单批量缓存操作、简单并发扣减场景,使用MULTI+WATCH完全够用;但支付、订单这类对数据一致性要求极高的业务,要么使用Lua脚本,要么把核心事务逻辑交给MySQL处理。