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事务分为两大阶段:
- 组队阶段(入队):执行
MULTI开启事务,后续输入的所有命令不会立刻执行,全部存入事务队列,控制台返回QUEUED排队标识;中途不想提交可以用DISCARD清空整个队列,直接放弃本次事务。 - 执行阶段(批量运行):输入
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=100、name=eric真实存入Redis,报错语句直接跳过,没有任何回滚操作。
官方不做回滚的设计原因(面试标准答案)
- Redis核心定位是高性能缓存,回滚机制会增加底层代码复杂度,大幅降低执行效率;
- 语法、数据类型错误都属于开发阶段bug,上线前单元测试就能提前规避,不需要线上兜底回滚;
- 如果需要完整原子执行、出错全部回滚的能力,优先使用Lua脚本。
四、Redis事务 VS MySQL事务 ACID对比
很多面试官会提问:Redis事务满足标准ACID吗?一张表讲清差异:
| 四大特性 | MySQL事务 | Redis MULTI事务 |
|---|---|---|
| 原子性(A) | 完全满足,要么全部成功,失败全部回滚 | 残缺,执行过程不插队,但单条命令出错不会回滚其他操作 |
| 一致性© | 内置约束保障,异常自动修复数据合法 | 无内置机制,一致性需要业务代码自行维护 |
| 隔离性(I) | 提供四大隔离级别,读写互不干扰 | 无隔离级别概念,事务未提交的数据全局可见 |
| 持久性(D) | 提交后数据永久落盘 | 和事务无关,由RDB/AOF持久化配置控制 |
Redis事务独有两个特性
- 串行执行:EXEC运行期间,Redis单线程只处理当前事务队列,外部请求全部阻塞;
- 无中间态:组队阶段的修改不会对外可见,只有提交后才生效。
五、Redis事务适用&不适用场景
适合使用
- 批量缓存写入,多条命令需要连续执行、不被其他请求打断;
- 简单并发控制:库存扣减、账户余额变更,搭配WATCH乐观锁;
- 轻量统计类批量操作,对数据一致性要求不高。
不推荐使用
- 金融核心业务(转账、订单支付),零容忍数据错乱;
- Redis集群环境,多键分属不同slot时,MULTI无法批量操作;
- 复杂多分支业务逻辑,需要完整原子回滚;
补充:需要强原子性统一用Lua脚本,脚本执行全程不可中断,出错不会半写入数据。
六、期末/面试高频简答题汇总
Redis事务分为哪两个阶段?
答:组队阶段(MULTI入队,命令标记QUEUED)、执行阶段(EXEC批量执行),DISCARD命令可以直接丢弃未提交的事务队列。WATCH命令的作用是什么?
答:实现Redis乐观锁,监控指定key;若事务提交前key被外部客户端修改,EXEC会返回nil,整个事务作废。Redis事务为什么不支持回滚?
答:Redis主打高性能,回滚会增加底层复杂度;语法、类型错误属于开发bug,测试阶段可提前发现;需要完整原子操作推荐Lua脚本。两种事务报错处理有什么区别?
答:①入队时语法错误:整个事务直接作废;②组队无错、执行时报错:正常命令正常保存,错误语句跳过,不会回滚。Redis事务完整支持ACID吗?
答:不支持。原子性残缺、无隔离级别,一致性需要业务代码维护,持久性由持久化配置决定,和事务本身无关。
结尾小结
之前一直用MySQL事务的固有思维写Redis代码,踩了不少坑,现在彻底分清两者的区别:Redis事务只是命令批量打包工具,不是传统关系型数据库的标准事务。
日常简单批量缓存操作、简单并发扣减场景,使用MULTI+WATCH完全够用;但支付、订单这类对数据一致性要求极高的业务,要么使用Lua脚本,要么把核心事务逻辑交给MySQL处理。
