数据库事务的理解?
数据库事务(日志)--> mysql-driver.jar --> Java (jdbc) -->mybatis -->Spring事务 -->SpringBoot事务
事务概念
事务通常用于数据库领域,事务是指对数据库进行读或写的一组操作序列,要么都执行,要么都不执行,不允许只执行一部分的情况;
事务的操作结果只有两种,一种是操作成功,一种是操作不成功恢复到操作之前的状态;
事务的特性:(ACID)
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
比如帐号A向帐号B汇款需要6个操作:
1、从A账号中读出余额(1000)
2、对A账号做减操作(1000-100)
3、把结果更新到A账号中(900)
4、从B账号中读出余额(1000)
5、对B账号做加操作(1000+100)
6、把结果更新到B账号中(1100)
原子性:
保证1-6所有步骤要么都执行,要么都不执行,一旦在执行某一步骤的过程中发生异常,就需要执行回滚操作。比如执行到第五步时,B账户突然不可用(比如被冻结或其他原因不可用),那么之前的所有操作都应该回滚到执行事务之前的状态;
一致性:
在转账之前,A和B的账户中共有1000+1000=2000元钱,在转账之后,A和B的账户中共有900+1100=2000元。即数据的状态在执行该事务操作之后从一个状态改变到了另外一个状态,并保证数据在业务意义上是正确的;
隔离性:
隔离性是指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行,也就是多个事务之间貌似是排队进行的。
持久性:
一旦转账成功(事务提交),两个账户中的金额就会发生真实变化(会把数据写入数据库持久化保存,也就是永久的保存),这就是持久性;
当然在实际应用场景中一个节点属于单点故障,所以需要增加多个副本(replica)一起来保证持久;如果做得更加完善,可以在不同地理位置的另一个数据中心做备份。
事务的隔离级别
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 会 会 会
读已提交(read-committed) 不会 会 会
可重复读(repeatable-read) 不会 不会 会
串行化(serializable) 不会 不会 不会
MySQL 默认采用的REPEATABLE_READ隔离级别(可重复读);
Oracle 默认采用的READ_COMMITTED隔离级别(读已提交);
事务并发带来的问题
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据;
2、不可重复读:事务A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了修改并提交,导致事务A多次读取同一数据时,结果不一致。
3、幻读:事务A对数据库添加或删除了数据,但在事务B中也依然读取不到,也就是一个事务在开始读取数据时就像做了标记一样,只能读取当此刻的数据,后面数据库发生的变化,这个读取事务是无法感知的,是读不到的,那么这样也有一个问题,那就是幻读,读取到的数据与数据库真实的数据不一致,产生了幻像;
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除,解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表;
Spring事务管理源码分析
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
1、Spring读取xml后得到一个BeanDefination;
2、把BeanDefination放入到一个beanDefinitionMap(属于DefaultListableBeanFactory);
<tx:annotation-driven transaction-manager="transactionManager" />
相当于配置了:
TransactionalEventListenerFactory
org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator
AnnotationTransactionAttributeSource
TransactionInterceptor
BeanFactoryTransactionAttributeSourceAdvisor
1、SpringIOC容器里面应该是具体的单例bean对象,而BeanDefination还不是具体单例bean对象,BeanDefination只是对一个bean对象的定义描述信息;
2、创建具体的单例bean对象,通过反射:ctor.newInstance(args)
3、创建bean对象会基于BeanPostProcessor对事务操作的bean进行代理(jdk动态代理、cglib动态代理)
4、执行目标方法,被动态代理对象拦截,在拦截方法里面:
开启事务:
//TODO 设置自动提交为falsecon.setAutoCommit(false);
提交事务:
//TODO jdbc连接提交事务con.commit();
回滚事务:
//TODO JDBC回滚事务con.rollback();
Spring事务传播行为
事务传播行为是指多个具有事务的方法,它们相互调用时,方法的事务如何工作?
比如在事务A方法里面调用事务B方法,这时事务B方法会采用什么方式进行事务管理;
public class ServiceA {
public void methodA () {
serviceB.methodB ();
}
}
public class ServiceB {
public void methodB () {
}
}
Spring支持7种事务传播行为;
1、PROPAGATION_REQUIRED:Spring默认使用该事务,如果当前没有事务,就新建一个事务,如果当前已经存在一个事务中,就直接加入到当前这个事务中,这是使用最多的事务传播行为,也是Spring默认的事务传播行为。
比如,ServiceB.methodB的事务传播定义为PROPAGATION_REQUIRED, 那么执行ServiceA.methodA的时候:
(1)如果ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务,这时直接共用外部事务,所以此时ServiceA.methodA或者ServiceB.methodB无论哪个发生异常,methodA和methodB作为一个整体都将一同回滚。
(2)如果ServiceA.methodA没有事务,ServiceB.methodB就会为自己新建一个事务,此时在ServiceA.methodA中是没有事务管理的,ServiceB.methodB内的任何地方出现异常,ServiceB.methodB将会被回滚,但不会引起ServiceA.methodA的回滚;
2、PROPAGATION_REQUIRES_NEW:新建一个内部事务,如果当前存在外部事务,把当前外部事务挂起,内务事务结束时, 外部事务将继续执行;
比如ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA方法的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成后,ServiceA.methodA方法的事务再继续执行。
由于ServiceB.methodB是新起的一个事务,那么就是存在两个不同的事务。
(1)如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。
(2)如果ServiceB.methodB失败回滚,如果它抛出的异常被ServiceA.methodA的try..catch捕获并处理,ServiceA.methodA事务仍然可能提交,如果ServiceB.methodB抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚;
3、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作;
嵌套事务是外部事务的一个子事务,是外部事务的一部分, 只有外部事务结束后它才会被提交;
比如ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_NESTED,那么当执行ServiceB.methodB时,ServiceA.methodA所在的事务会被挂起,ServiceB.methodB会起一个新的子事务并设置savepoint,等待ServiceB.methodB的事务完成以后,ServiceA.methodA所在的事务才继续执行,由于ServiceB.methodB是外部事务的子事务,那么
(1)如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB也将回滚。
(2)如果ServiceB.methodB失败回滚,如果它抛出的异常被ServiceA.methodA的try..catch捕获并处理,ServiceA.methodA事务仍然可以提交;如果它抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚;
PROPAGATION_NESTED与PROPAGATION_REQUIRES_NEW的区别是:
PROPAGATION_REQUIRES_NEW 完全是一个新的事务,它与外部事务相互独立;而PROPAGATION_NESTED则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 外部事务roll back,嵌套事务也会被roll back;
4、PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行;
5、PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常;
6、PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起;
7、PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。