Redis事务
1. 事务
Redis的单条命令
是保证原子性
的,但是Redis事务不能保证原子性
Redis事务本质:一组命令的集合。
步骤:
- 开启事务
- 操作一组命令(所有命令放入一个队列)
- 执行事务
事务中每条命令都会被
序列化
,执行过程中按顺序执行,不允许其他命令的干扰。
- 一致性
- 顺序性
- 排他性
- Redis事务没有隔离级别的概念
1.1 Redis事务操作过程
- 事务开启(
multi
) - 命令入队
- 执行事务(
exec
)
1.1.1 事务的开启与执行
所有事务中的命令在加入时都没有被执行,直到提交时才会开始执行(exec)一次性完成。
案例:
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379>
1.1.2 取消事务(discard)
案例:
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI # 当前未开启事务,无法执行
127.0.0.1:6379> get k1 # 被放弃事务中的命令并未执行
(nil)
1.2 事务错误
1.2.1 编译时异常
代码语法错误(
编译时异常
)所有的命令都不执行
案例:
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> key * # 遇到语法错误异常
(error) ERR unknown command `key`, with args beginning with: `*`,
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec # 提交事务失败
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 所有命令都不会被执行
(nil)
1.2.2 运行时异常
代码逻辑错误(
运行时异常
) 其他命令可以正常执行 >>> 所以不保证事务的原子性
案例:
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> incr k1 # 逻辑错误(对字符串无法进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行
虽然中间有一条命令报错了,但是后面的指令依旧执行成功了,所以所Redis单条命令保证原子性,但是Redis不能保证原子性。
总结:
- 编译时异常:所有命令都不会被执行
- 运行时异常:错误命令不会被执行,其他命令正常执行
1.3 监控
1.3.1 悲观锁
- 概念
悲观锁,顾名思义:很悲观,在每次事务当中,都认为有人会更改数据,所以提前把数据锁住,一种以悲观的态度来防止一切数据冲突。
在修改数据之前把数据锁住,然后对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后,下一个人对数据加锁才能操作数据
一般数据库本身锁的机制都是基于悲观锁的机制来实现的
- 特点
可以完全保证数据的独占性和正确性,因为每次请求都会对数据进行加锁,然后进行数据操作,最后再解锁,而加锁解锁的过程会造成消耗,所以性能不高; - 实现
-
mysql 悲观锁的方法:
-
开启事务:
start transaction;
-
查询信息
select status from table where id = 1 for update # for update加锁
-
根据信息生成订单
insert into table111(id, goods_id) vlaues(null,1);
-
修改商品
update table set status = 2 where id =1;
-
提交信息
commit; # commit释放锁
-
-
Java悲观锁的实现
- Java 中悲观锁的实现包括 synchronized 关键字和 Lock 相关类等,我们以 Lock 接口为例,例如 Lock 的实现类 ReentrantLock,类中的 lock() 等方法就是执行加锁,而 unlock() 方法是执行解锁。处理资源之前必须要先加锁并拿到锁,等到处理完了之后再解开锁。
-
1.3.2 乐观锁
-
概念
- 很乐观,认为什么时候都不会出现问题,所以不会上锁!在更新数据的时候判断一下,在此期间是否有人修改过这个数据
- 获取version
- 更新的时候比较version
-
实现
- 给表添加version字段,模拟两个线程,线程A和线程B同时执行修改一条数据
- 线程A执行更新
update table set goods='goods_1',version=version+1 where id = 1 and version = 0;
- 线程B执行更新,此时数据已经被修改了,所以线程B执行不成功
update table set goods='goods_1',version=version+1 where id = 1 and version = 0; # 判断version,是否有人修改过这个数据
- 实现乐观锁控制
如果不使用乐观锁控制,AB同时操作一条数据,会造成一条数据被修改了两次,产生脏数据,例如买票,只剩下一张票,结果AB都拿到了票,数据库发现票数为-1的情况。
1.3.3 监控数据(watch key)
使用watch key 监控指定数据,相当于乐观锁加锁
正常执行
127.0.0.1:6379> set money 100 # 设置余额:100
OK
127.0.0.1:6379> set use 0 # 支出使用:0
OK
127.0.0.1:6379> watch money # 监控money(上锁)
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby use 20
QUEUED
127.0.0.1:6379> exec # 监视值没有被被中途修改,事务正常执行
1) (integer) 80
2) (integer) 20
127.0.0.1:6379>
测试多线程修改值,使用watch可以当作redis的乐观锁操作( 相当于getversion )
启动另外一个客户端模拟插队线程
-
线程1:
# money: 100 use: 0 127.0.0.1:6379> watch money # 上锁 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby money 20 QUEUED 127.0.0.1:6379> incrby use 20 QUEUED 127.0.0.1:6379> # 此时事务还没有结束,还没执行事务
-
线程2插队:
127.0.0.1:6379> incrby money 500 # 修改了线程1中监视的money (integer) 600 127.0.0.1:6379>
-
回到线程1,执行事务:
127.0.0.1:6379> exec # 执行之前,另外一条线程修改了money,这个时候就会导致事务执行失败 (nil) # 没有结果,说明事务执行失败 127.0.0.1:6379> get money # 线程2,修改生效 "600" 127.0.0.1:6379> get use # 线程1事务执行失败,数值没有修改 "0"
解锁(
unwatch
)
unwatch进行解锁获取最新值,然后再加锁进行事务。
注意:
每次提交执行exec后都会自动释放锁,不管是否成功
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!