事务特性
实现事务必须满足以下四大特性:
- Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行。
- Consistency(一致性):数据库在事务执行前后,完整性没有被破坏。 (转账前后,钱的总数不变)
- Durability(持久性):事务执行成功后必须全部写入磁盘。
- Isolation(隔离性):允许多个并发事务同时对数据进行操作,也不会由于交叉执行导致数据不一致。
隔离性 通过MVVC实现(相对应的有MVCC)
原子性、一致性、持久性:通过数据库的redo log、undo log、Force Log at Commit实现
数据库是可以控制事务的传播和隔离级别的,Spring在之上又进一步做了封装,本质上是同一概念。
本文不对数据库事务做过多发散,主要介绍Spring事务相关的部分,数据库事务详情放在另外知识点。
事务隔离级别
事务的四大特性之一:隔离性 ,隔离性在数据库事务中有四种级别。
而Spring则在其基础上加了一种DEFAULT,这是PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
其他四种隔离级别如下:
假设有两个事务交替执行,分别为事务A、事务B
READ_UNCOMMITTED未提交读
事务A做了数据的更改,而事务B能看到事务A未提交的更新数据;
这种隔离级别可能会产生脏读,比如事务A把数据更改撤销了。
READ_COMMITTED提交读
事务A做了数据的更改,事务B只有等事务A提交更改数据后,才能读到新数据。
这种隔离级别可能会产生不可重复读,比如事务B在事务A提交前读第一次,提交后读第二次,读到的数据可能不一致。
REPEATABLE_READ可重复读
可重复读是Mysql InnoDB默认隔离级别。
事务B开始了事务,不管事务A是否提交修改的数据,事务B多次读到的数据都是原先一致。
这种隔离级别可能会产生幻读,比如注解为id的数据:
事务A:select * from users where id = 1; //先看看id是否存在
事务B:insert into users(id, name) values (1, '事务B拥有'); //事务B占用了id=1
事务A:insert into users(id, name) values (1, '事务A拥有'); //事务A继续查id=1,还是空的,但是执行插入报错
事务A发生了幻读,读的是鬼影。(这里容易混淆,不可重复读侧重表达 读-读,幻读侧重表达 读-写)
SERIALIZABLE可串行化
对事务A读取到的数据进行加锁,事务B无法对该数据做修改操作,会阻塞住。只有等A事务提交事务后,修改操作才会执行。
隔离级别越高,对并发的影响就越大。可串行化容易导致超时和锁争用问题。
事务隔离级别设置
在Spring种,通过[@Transactional](https://my.oschina.net/u/3770144)
注解的isolation属性,就可以设置当前方法的事务级别
Spring在开启事务时,会对当前会话设置事务隔离级别;所以当Spring设置的隔离级别和数据库的隔离级别不一致,Spring的隔离级别会生效。
Spring事务传播机制
Spring通过 AOP环绕通知进行拦截 来实现事务,我们不需要关心事务的开始、提交、回滚,只需要在方法上添加[@Transactional](https://my.oschina.net/u/3770144)
注解就可以了。
但是带事务的方法A调用普通方法B,算不算同一个事务呢?异常了是否会一起回滚呢?诸如此类问题,事务传播机制就能解决。
通过[@Transactional](https://my.oschina.net/u/3770144)
注解的propagation=Propagation.XXXX
属性,就可以设置事务传播机制。机制类别有以下:
假设 方法A 调用了 方法B
REQUIRED(默认机制)
- 方法B设置了REQUIRED,如果方法A有事务,方法B就加入当前事务,合并为一个事务(如上图)
- 如果方法A没有事务,方法B则会自己新键一个事务
SUPPORTS
- 方法B设置了SUPPORTS,如果方法A有事务,方法B就加入当前事务,合并为一个事务
- 如果方法A没有事务,则方法B不开启事务
NOT_SUPPORTED
- 方法B设置了NOT_SUPPORTED,如果方法A有事务,方法A的事务会被挂起。方法B以非事务的状态执行完,再继续执行方法A的事务。
- 用于缩小事务范围的场景,将一些无需事务包围的代码写到NOT_SUPPORTED级别的方法中。
MANDATORY
- 方法B设置了MANDATORY,如果方法A有事务,方法B就加入当前事务,合并为一个事务
- 如果方法A没有事务,则会抛出异常,保证上下文调用代码不要遗漏事务。
NEVER
- 方法B设置了NEVER,如果方法A有事务,则抛出异常
REQUIRES_NEW
- 方法B设置了REQUIRES_NEW,如果方法A有事务,方法A的事务会被挂起。方法B创建一个新的事务执行,执行完后再继续执行方法A的事务。
- 方法A和方法B是两个单独的事务,自己的事务出现问题只回滚自己的事务。
NESTED
- 方法B设置了NESTED,如果方法A存在事务,方法B就会称为A事务的子事务。方法B执行结束后并不会提交,等方法A结束后才提交。
- 如果方法A没有事务,方法B则新建事务。
- 如果方法B异常,方法A正常提交;如果方法A异常,方法B回滚。
事务失效场景
数据库引擎不支持
MyISAM不支持事务,需要事务的一般用InnoDB引擎
没有被Spring容器管理
数据源没有配置事务管理器
非public方法
[@Transactional](https://my.oschina.net/u/3770144)
注解只能添加到public方法上
异常被捕捉
需要抛出异常,事务才能回滚
抛出异常类型错误
默认捕捉的是RuntimeException,而抛出的为Exception
自身调用
只有在外部调用事务才会生效
一个普通方法调用一个事务方法
一个事务方法调用另一个事务方法
- spring是通过代理来管理事务的,同类调用没有走代理
解决方式就是改为外部调用,拆成两个Service
如果非要在内部调用,可以使用代理对象去调用