数据库事务
1. 事务4个特性
1)原子性(atomic)
一个事务可能包括多个数据操作,在事务中,这些操作要么都成功,要么都失败,不能部分成功、部分失败
2)一致性(consistency)
这里是相对于业务数据的一致性,不管成功与否,业务数据本身没有出现异常、或是破坏
3)隔离性(isolation)
各自事务在自己的数据空间执行,不会对其它事务产生干扰。
4)持久性
事务成功执行后,执行结果需要持久化到数据库中。
2. 并发带来的数据问题
1) 脏读(dirty read)
读了未提交的事务数据,如果这个事务后来回滚了,那整个数据一致性就破坏了
脏读举例.png
2)不可重复读(unrepeatable read)
事务在过程读取了两次数据,两次数据不一致,原因是另一个事务在这个过程中变更了这个数据
不可重复读.png
3)幻象读(phantom read)
在相同的where条件下,第一次读出了2条数据,第二次读出了3条数据,原因是另一个事务新增了一条数据
幻读举例.png
注:不可重复读与幻象读区别
不可重复读是对事务数据作了更新、删除,而幻象读则是从无到有,忽然出现的感觉,也就是新增的数据
4)第一类丢失更新
事务一回滚了事务,而这个过程中,另一个事务变更了数据,回滚的事务一会导致事务2的数据更新丢失
第一类丢失更新举例.png
5)第二类丢失更新
事务一提交了事务,但另一个事务也变更了数据,事务1的提交导致事务2的变更数据丢失
第二类丢失更新.png
3. 事务隔离级别
ANSI/ISO SQL92标准定义了4个等级的事务隔离级别,不同的级别,对于以上并发出现的问题的解决能力是不同的。
不同隔离级别差别.png
当然隔离级别越高,并发性也会越低。
我们可以查看一下我们当前的事务隔离级别,当前为可重复读
事务隔离级别.png
4. 事务管理概念抽象
Spring将事务管理抽象为3个抽象:
- 事务定义
- 事务状态
- 事务管理器
事务定义
事务隔离:
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED,
ISOLATION_REPEATABLE_READ,
ISOLATION_SERIALIZABLE,
还有一个默认隔离级别ISOLATION_DEFAULT,它表示使用底层数据库的默认隔离级别。
事务传播:
大多数情况下数据库操作会在一个事务中运行,但有时也可能有一些其他情况,如新启事务,不参加事务等,spring为我们提供了这些支持。
事务传播类型.png
事务超时:
限制事务的运行时间,超过时间后,事务被回滚
只读事务
不能修改任何数据,这样事务管理器可以针对只读事务作一些优化措施
事务状态
代表事务的运行状态:是否新事务、事务是否已完成、事务是否被标识为rollbackOnly、事务设置为rollbackOnly, 还包括如保存点的相关操作,如当前事务是否创建了保存点、创建保存点、回滚到保存点、释放保存点等。
事务管理器
spring将事务管理功能,委托给底层具体的持久化实现框架,这样就可以以相同的方式来统一进行管理。
不同的事务管理器实现.png
5. Transaction注解用法实例
先看一下我们的准备
1)我们的表结构
商品表结构.png
很简单,一个自增主键,一个名称,库存数量,还有两个时间
2) 我们初始的数据
初始数据.png
现在就一条数据,小米汽车,库存100辆
只读属性readOnly
下面是我们的示例代码,就是更新一下库存数量,但事务的注解是只读,不能修改
只读属性显示.png
调用这个service方法后,我们会看到如下的错误提示:
只读示例运行结果.png
当我们把readOnly改为false,或去掉的时候,就可以成功更新了
成功更新了数量.png
超时时间timeout
事务超时时间,过了这个时间,事务就失败了,当然库中的数据也不会变化
事务超时时间.png
下面是超时异常的提示
超时异常.png
异常不回滚noRollbackFor
下面代码为算术异常不回滚
异常不回滚示例.png
可以看到,事务成功提交了,库表中的数据也更新了,当然,异常也发生了
事务提交,异常也生了.png
异常回滚 rollbackFor
spring默认运行时异常自动回滚,而检查型异常不回滚。
下面代码中有一个检查型异常Class.formName("abc"),如果类不存在的话,会抛出ClassNotFoundException的检查型异常,
如果rollbackFor没有指定这个类的话,就不会回滚事务,而指定了这个类的话,就会发生回滚的情况
异常回滚.png
从下面结果可以看出:事务发生了回滚,表中的数据没有更新,当然异常也是发生的
image.png
隔离级别isolation
1)读未提交
下面的代码模拟,隔离级别为读取未提交,这样其实是发生了上面的脏读的情况
读取未提交数据.png
2)读已提交
这里仍会发生不可重复读的情况
已提交事务实例.png
看一下输出结果:
21:46:01.050480700,执行完更新语句 //这里执行完了语句,但还没有提交事务
21:46:02.230455300,当前库存为:26 //这里读取到的是旧数据
21:46:06.058713500,更新数据成功! //这时已经成功修改数据,提交了事务
21:46:12.231810700,当前库存为:27 //这时我们是可以读取到提交的数据了
3)可重复读
这里我们只调整一个地方,将隔离级别改为可重复读
可重复读举例.png
这里,我们看一下结果,读取到的数量都是27,中间修改的数量不受影响
21:56:57.629283,执行完更新语句
21:56:58.905637200,当前库存为:27
21:57:02.637680200,更新数据成功!
21:57:08.905904500,当前库存为:27
下面我们看看幻读的情况:
幻读举例.png
这里我们看出:抛出了异常,新增的一条记录,却没有读取到,也就是在这种模式下,更新和新增都是读取不到的
输出结果.png
这里我们可以初步得出一个结论:mysql在innodb引擎下,隔离级别为可重复读时,并没有发生幻读的情况
事务传播特性
1)PROPAGATION_NEVER 非事务方式
这里我们有两个方法,addNum方法调用updateNum方法,updateNum方法事务传播为非事务方法
never事务传播.png
这里我们会看到事务发生了回滚,子方法不支持事务,抛出了异常
never事务异常.png
注:这里有一个细节要注意一下,不能直接调用updateNum方法,否则子方法的传播行为会失效,
因为直接调用的话,使用的是目标对象的方法,并没有使用事务代理类的方法,不受事务代理控制
当然,还有一个方法,就是使用当前的事务代理对象
service方法上需要打开这个暴露代理的开关,然后,得到当前代理后再调用方法,就可以了,
当然,这个例子里是抛异常,回滚事务哈
使用当前事务代理对象.png
使用这个还需要引用一下aop的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2)PROPAGATION_REQUIRED 需要事务
当前有事务,加入到事务中,没有事务,则新建一个事务
当前方法没有事务,子方法为required类型,会创建一个新事务,在事务中运行
required事务支持.png
我们看到,进入子方法后才开启事务的
required事务输出.png
我们来看异常的例子
1)当前方法和子方法在一个事务,子方法异常,整个事务将会回滚
required抛异常例子.png
全程只开启了一个事务,并且作了回滚
image.png
2)如果子方法中,捕获了异常,会是什么结果呢
子方法中捕获异常.png
答案是:两个方法都生效了,并没有回滚事务
两个方法都生效.png
3)如果是当前方法抛出异常呢,答案是整体都会回滚,因为整体是一个事务
当前方法抛出异常.png
4)如果是当前方法捕获了异常呢?
跟上面的情况相似,因为是捕获了,所以事务会正常提交
当前方法捕获异常.png
可以看到两条数据,都成功更新了
image.png
!这里有一个友情提示哈:
这里说的是运行时异常,如果是检查型异常,即使抛到了外层,都不会回滚,除非是指定了rollbackFor属性
3)PROPAGATION_SUPPORTS 支持当前事务
当前有事务,则加入事务,当前没有事务,则以非事务方式运行
1)加入当前事务
事务支持.png因为共用同一个事务,所以子方法抛异常,整个事务回滚
2)非事务方式
image.png这里没有事务生效,都可以成功提交,也正常抛出了异常
image.png
想回滚,但当前没有事务,无需回滚
4)PROPAGATION_MANDATORY 强制事务
必须在事务环境中运行,外围没有事务,会报错
image.png
image.png
可以看到外围没有事务时,它也不工作了,直接抛了异常
5)PROPAGATION_REQUIRES_NEW 启新事务
新启一个事务,旧事务挂起
这里,我们看到许多休眠时间,主要是为了看一下真实执行的时间
启新事务实例.png
这里可以看到,最后两个都成功了,两个时间相差了5秒,addNum方法先执行成功,updateNum后执行,这5秒为两个方法之间休眠的时间
执行结果.png
通过debug模式,我们可以很清楚看到两个事务的执行过程
执行结果.png
注:可以关注一下里面的时间,保存到库中的更新时间,是事务过程中语句的执行时间,而不是事务的提交时间
6)PROPAGATION_NOT_SUPPORTED 非事务方法方式
以非事务方式运行,如果环境有事务,则挂起
非事务方式实例.png
我们可以看结果,子方法成功提交了,主方法事务发生了回滚
image.png
7)PROPAGATION_NESTED 嵌套事务
如果外部事务失败,嵌套事务会被回滚,而嵌套事务失败了,外部事务可以根据需要回滚或提交
1)嵌套事务异常
嵌套事务举例.png
可以看到回滚的流程,嵌套事务先回退到保存点,外部事务再回滚
嵌套事务输出.png
当然:如果嵌套事务中捕获了异常,两个事务都会提交
2)外部事务异常
虽然嵌套事务没有异常,但因为外部事务回滚了,所以整体都回滚了
image.png
当然,如果外部事务中捕获了这个异常,还会成功提交吗,答案是肯定的,都可以成功提交。
感觉对您有用,可以点个赞哦!