前言: java开发中,事务回滚是必须要懂的知识点,如果对事务不了解,只是简单的以为错了就回滚,那就太过于表面了。此文章仅代表鄙人的总结和理解,如有错漏,欢迎指正...
一、事务是什么?
事务是一组原子操作单元,从数据库角度说,就是一组SQL指令,要么全部执行成功,若因为某个原因其中一条指令执行有错误,则撤销先前执行过的所有指令。更简答的说就是:要么全部执行成功,要么撤销不执行。
一、事务所具有的四种特性
原子性: 个人理解,就是事务执行不可分割,要么全部完成,要么全部拉倒不干。
一致性: 关于一致性这个概念我们来举个例子说明吧,假设张三给李四转了100元,那么需要先从张三那边扣除100,然后李四那边增加100,这个转账的过程对于其他事务而言是无法看到的,这种状态始终都在保持一致,这个过程我们称之为一致性。
隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据是独立的;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
三、事务ACID的隔离级别和传播属性
七个事务传播属性
1. Propagation.REQUIRED:支持当前事务,如果当前没有事务,则新建一个事务,默认使用这种,也是最常见的.
2. Propagation.SUPPORTS:支持当前事务,如果没有事务,就以非事务的方式执行.
3. Propagation.MANDATORY:支持当前事务,如果没有事务,就抛出异常.
4. Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,就把当前事务挂起.
5. Propagation.NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,就把当前事务挂起.
6. Propagation.NEVER:以非事务的方式执行,如果当前存在事务,则会抛出异常.
7. Propagation.NESTED:如果当前事务存在,则执行嵌套事务,否则执行类似REQUIRED的操作.
五种隔离级别
DEFAULT(默认):
是事务管理器的默认隔离级别,使用数据库默认的隔离级别,另外四个与jdbc的隔离级别相对应
READ_UNCOMMITTED(读未提交):
最低的隔离级别 ,它允许一个事务读取另一个事务未提交的数据,会产生脏读,不可重复读,幻读
READ_COMMITTED(读已提交):
保证一个事务修改的数据提交后另一个事务才能读取到,可以避免脏读. (oracle默认使用使用)
REPEATABLE_READ(可重复读):
数据库就是使用的这种隔离级别,可以避免脏读和不可重复读,但是可能出现幻读(幻读:一个事务读取完,另一个事务提交了更新,本事务再次读取会发现前后数据不一致,像产生了幻觉一样,所以叫幻读)。(mysql默认使用使用)
SERIALIZABLE(串行化):
花费代价最高也是最可开的事务隔离级别,事务被处理为顺序执行,但是这种隔离级别会产生锁表,就是一个事务读取之后,另一个事务必须等待这个事务完成,他才可以进行,第一个事务会将整张表锁起来,一般不会使用这种隔离级别,性能极低!
名词解释:
脏读: 在一个事务处理中,读取了一个未提交完整的事务数据,有可能会回滚,所以出现脏读
不可重复读: 读取了前一个事务提交的数据,然后另一个事务又再更新,导致每次读的都不一样
幻读: 一个事务修改了一整批的数据整体,另一个事务又去更新了某一条数据,导致整体数据不统一,让人感觉幻读
使用方式:
在springboot项目中使用注解:@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
注意:一个事务内部,没有隔离的概念的。打个比方:在同一个事务里,先对一条记录执行更新操作,然后再执行查询操作,整个事务没有完全的执行完毕,那么在执行查询的时候,虽然事务还没结束,但是查询的仍然是最新的更新的值,如果是另外一个事务查询这条记录,因为第一个事务并没有完全结束,所以查询到的就是老值。
四、@Transactional 事务不生效的场景
spring的事务实现原理为AOP,只有通过代理对象调用方法才能被拦截,事务才能生效
1.private、final、static方法,事务不生效,入口方法必须是public,spring的AOP特性决定的,spring认为private自己用的方法应该自己控制,不应该用事务切进去
2、Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚(至于为什么spring要这么设计:因为spring认为Checked的异常属于业务的,coder需要给出解决方案而不应该直接扔该框架)
3.同类调用不生效(service方法中调用本类中的另一个方法,事务没有生效):
在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的
3.同类调用不生效(service方法中调用本类中的另一个方法,事务没有生效):
在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的
4.如果使用的是rollbakfor的默认,已检查的异常(所有派生自Error和RuntimeException的类,都是未检查异常.其余的是已检查异常, 比如nullPointException是未检查的,IllegalAccessException 是已检查的)不回滚,可设为rollbackFor={Exception.class}
5.最好不要把@trasaction注解到接口上:
在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
6、确认你的类是否被代理了(因为spring的事务实现原理为AOP,只有通过代理对象调用方法才能被拦截,事务才能生效
7、确保你的业务和事务入口在同一个线程里,否则事务也是不生效的
五:事务执行过程中发生宕机怎么处理?
问题:数据库插入百万级数据的时候,还没操作完,但是服务器重启了,数据库会继续执行还是直接回滚?
答: 不会自动继续执行也不会自动直接回滚。但可以依据事务日志手动选择继续执行还是回滚。
详解: 事务开启时,事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是人们口中常说的“日志先行”(Write-Ahead Logging)。
日志分为两种类型:redo log和undo log
(1)redo log
在系统启动的时候,就已经为redo log分配了一块连续的存储空间,以顺序追加的方式记录redo log,通过顺序io来改善性能。所有的事务共享redo log的存储空间,它们的redo log按语句的执行顺序,依次交替的记录在一起。如下一个简单示例:
记录1:<trx1, insert…>
记录2:<trx2, delete…>
记录3:<trx3, update…>
记录4:<trx1, update…>
记录5:<trx3, insert…>
此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据redo log中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。
(2)undo log
undo log主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。
以下是undo+redo事务的简化过程,假设有2个数值,分别为A和B,值为1,2
1.start transaction;
2.记录 A=1 到undo log;
3.update A = 3;
4.记录 A=3 到redo log;
5.记录 B=2 到undo log;
6.update B = 4;
7.记录B = 4 到redo log;
8.将redo log刷新到磁盘
9.commit
在1-8的任意一步系统宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响。如果在8-9之间宕机,恢复之后可以选择回滚,也可以选择继续完成事务提交,因为此时redo log已经持久化。
若在9之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后,可以根据redo log把数据刷回磁盘。所以,redo log其实保障的是事务的持久性和一致性,而undo log则保障了事务的原子性。
六、事务课题延伸--tcc分布式事务实现原理
tcc分布式的原理分为3个阶段:
1.try(数据库表中采用临时字段,先预扣该数量)
2.Confirm(正式提交阶段)
3.Cancel(如果1或者2失败就会走回滚阶段)
开源的tcc分布式框架:
ByteTCC,TCC-transaction,Himly(推荐)
但是在实际系统的开发过程中,可能服务间的调用是异步的。也就是说,一个服务发送一个消息给 MQ,即消息中间件,比如 RocketMQ、RabbitMQ、Kafka、ActiveMQ 等等。
这个时候,就要用上可靠消息最终一致性方案,来实现分布式事务。
可靠消息最终一致性方案,很多情况会导致MQ集群崩掉导致错误 ,要做到高可用,推荐使用阿里开源的 RocketMQ,就实现了可靠消息服务的所有功能而且阿里巴巴开源的有做高并发的优化
详细知识请参考博客 --》 终于有人把“TCC分布式事务”实现原理讲明白了!
结语:以往都是看别人的博客进行学习技术,其中不乏有精华博客也有吊儿郎当的CV大法文章,所以决定将自己所学所用所整理的知识分享给大家,主要还是想为了后浪们少走些弯路,多些正能量的博客,如有错漏,欢迎指正,仅希望大家能在我的博客中学到知识,解决到问题,那么就足够了。谢谢大家!(转载请注明原文出处)