spring事务
事务的四大特性:
原子性:事务内的修改操作,要么都成功,要么都失败
一致性:数据的修改必须正确,保持一致,比如转账 A扣100,那么B必须加100
隔离性:两个不同的事务间的操作,不能相互影响,事务有四个隔离机制(下面会描述)
持久性:指的是事务提交后会长久保存,所以数据不能有错
脏读、幻读、不可重复读 (这是事务解决的几种问题):
脏读:指的是一次读取中,事务A对数据进行修改但是未提交,事务B读取了这条数据,但是事务A回滚,导致事务B读取了错误数据。
幻读:指的是两次读取,侧重点在于新增删除,事务A第一次读取数据,同时事务B对数据进行了新增或删除,事务A第二次读取这批数据时和第一次读有出入。
不可重复读:指的是两次读取,侧重点在于修改,事务A第一次读取数据,同时事务B对数据进行了修改,导致事务A两次读取的数据不一致。
事务的隔离级别(四种):
读未提交:当前事务可以读取其他事务修改但是未提交的数据
解决:一般不会使用,什么问题也解决不了
读已提交:当前事务可以读取其他事务修改并且已经提交的数据
解决:可以解决脏读
可重复读:当前事务在读取数据的时候会加上锁,让其他事务不能对当前数据修改。
解决:可以解决脏读,不可重复读
串行化:隔离级别最高的,让所有事务有顺序的执行,A事务执行完B事务再执行
解决:可以解决脏读、幻读、不可重复读问题,但是相对的执行效率会降低
2类事务丢失:
第一类:问题由多个事务间同时操作的回滚所导致,事务A和B共同修改age=10这条数据,事务A将age修改为20,在还没有提交的时候事务B也来操作,此时事务B拿到的还是age=10,因为undolog日志文件保存的还是事务A提交前的数据,此时事务A提交了事务,数据库数据变成了20,然后事务B运行过程报错,触发回滚,将数据改成了10,导致事务A修改操作无意义。
第二类:问题由多个事务同时操作数据导致,还是刚才的例子,事务A将数据修改为20,在提交前事务B也来修改,然后事务A提交,数据落地为20,然后事务B读取到的依然是10,此时事务B对数据操作并且提交,导致事务A的操作无意义。
区别则在于:一个是回滚导致A无效,一个是共同操作数据导致A无效。
@Transactional事务失效场景
用在被final、static修饰的方法上会失效:
由于spring事务会基于spring的动态代理来实现,通常使用到的是CGLIB动态代理,而这种动态代理方式会创建目标类的子类来实现,所以会导致动态代理无法继承目标方法。
非public 的方法会失效:
在事务底层代码中明确说明无法作用域非public方法。
多线程调用可能失效:
线程A指定线程B去执行新增数据,等线程B执行完毕后主线程A还是读不到新增的数据,那是因为不同线程处于两个不同的事务当中。
抛出的异常错误:
默认情况下@transactional会捕获runtimeException,如果其他异常会捕获不了所以失效,可以通过添加注解属性,rollbackFor = Exception.class 指定异常可以解决这问题
配合try catch使用:
如果在catch中只捕获异常却没有抛出异常也会失效,解决方式两种:1.抛出runtime异常让事务注解捕获。2.在catch中手动触发回滚方法
分布式事务:
2PC协议(两阶段提交)
1.应用程序通过事务协调器向两个服务同时发送通知,a和b同时开始执行各自本地事务,执行完了不提交事务给事务协调器返回yes或者no
2.如果两个事务返回的都是yes,那么发送指令让两个事务都提交事务,如果其中一方提交失败那么返回通知协调器,由协调器通知另一方也回滚
3.如果参与者中有返回no的,都直接回滚
消息队列完成最终事务一致
举例a服务新增订单b服务减少库存的例子
1.a服务完成自己本地事务,添加订单操作,并且在消息表中添加一条“减少库存”的消息
2.专门有定时任务检查消息表中的数据,并且取出来发给mq给到b消费
3.b收到减少库存的消息开始执行减少库存操作,并且在本地记录表新增“减少库存信息”(在每次减少库存前都会检查一遍记录表,是否执行过这个减少库存,如果执行过了就不要再执行)
4.b执行完了会给mq发送一条“库存减少”消息
5.a收到了“库存减少”消息后会删除消息表中的“减少库存”消息
如果b失败定时任务会不断给b发送重试。