一.事务隔离级别
我们回顾一下MySQL 事务隔离级别.
SQL 标准定义了四种隔离级别, MySQL 全都⽀持. 这四种隔离级别分别是:
1.
读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.
因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数据称之为脏数据, 这个问题称之为脏读.
2.
读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据,
该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读
3.
可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别。
⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去, 这个现象叫幻读
4.串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多.
在数据库中通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;
以上 SQL 的执⾏结果如下:
1.1Spring 事务隔离级别
Spring 中事务隔离级别有5 种:
1. Isolation.DEFAULT
: 以连接的数据库的事务隔离级别为主.
2.
Isolation.READ_UNCOMMITTED
: 读未提交, 对应SQL标准中
READ UNCOMMITTED
3. Isolation.READ_COMMITTED
: 读已提交,对应SQL标准中
READ COMMITTED
4.
Isolation.REPEATABLE_READ
: 可重复读, 对应SQL标准中
REPEATABLE READ
5.
Isolation.SERIALIZABLE
: 串⾏化, 对应SQL标准中
SERIALIZABLE
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置
@Transactional(isolation = Isolation.READ_COMMITTED)
@RequestMapping("/r3")
public String r3(String name,String password) throws IOException {
//... 代码省略
return "r3";
}
二.Spring 事务传播机制
2.1什么是事务传播机制
概念:
事务传播机制就是: 多个事务⽅法存在调⽤关系时, 事务是如何在这些⽅法间进⾏传播的.
⽐如有两个⽅法A, B都被
@Transactional
修饰, A⽅法调⽤B⽅法
A⽅法运⾏时, 会开启⼀个事务. 当A调⽤B时, B⽅法本⾝也有事务, 此时B⽅法运⾏时, 是加⼊A的事务, 还是创建⼀个新的事务呢?
这个就涉及到了事务的传播机制.
⽐如公司流程管理执⾏任务之前, 需要先写执⾏⽂档此时A部⻔有⼀项⼯作, 需要B部⻔的⽀援, 此时B部⻔是直接使⽤A部⻔的⽂档, 还是新建⼀个⽂档呢?
事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题
⽽事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题
2.2事务的传播机制有哪些
@Transactional 注解⽀持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为.
Spring 事务传播机制有以下 7 种:
1.
Propagation.REQUIRED
: 默认的事务传播级别. 如果当前存在事务, 则加⼊该事务. 如果当前没
有事务, 则创建⼀个新的事务
2.
Propagation.SUPPORTS
: 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以⾮事务的
⽅式继续运⾏
3.Propagation.MANDATORY
:强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则
抛出异常.
4.
Propagation.REQUIRES_NEW
: 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也
就是说不管外部⽅法是否开启事务,
Propagation.REQUIRES_NEW
修饰的内部⽅法都会新开
启⾃⼰的事务, 且开启的事务相互独⽴, 互不⼲扰.
5.
Propagation.NOT_SUPPORTED
: 以⾮事务⽅式运⾏, 如果当前存在事务, 则把当前事务挂起(不
⽤).
6. Propagation.NEVER
: 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.
7. Propagation.NESTED
: 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏.
如果当前没有事务, 则该取值等价于
PROPAGATION_REQUIRED
.
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
2.3Spring 事务传播机制使⽤和各种场景演⽰
对于以上事务传播机制,我们重点关注以下两个就可以了:
1. REQUIRED(默认值)
2. REQUIRES_NEW
2.3.1REQUIRED(加⼊事务)
看下⾯代码实现:
1. ⽤⼾注册, 插⼊⼀条数据
2. 记录操作⽇志, 插⼊⼀条数据(出现异常)
观察
propagation = Propagation.REQUIRED
的执⾏结果
@RequestMapping("/propaga")
@RestController
public class PropagationController {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/p1")
public String r3(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
//记录操作⽇志
logService.insertLog(name,"⽤⼾注册");
return "r3";
}
}
对应的UserService和LogService都添加上
@Transactional(propagation = Propagation.REQUIRED
)
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name,String op){
int a=10/0;
//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");
}
}
运⾏程序, 发现数据库没有插⼊任何数据
流程描述:
- p1 ⽅法开始事务
- ⽤⼾注册, 插⼊⼀条数据 (执⾏成功) (和p1 使⽤同⼀个事务)
- 记录操作⽇志, 插⼊⼀条数据(出现异常, 执⾏失败) (和p1 使⽤同⼀个事务)
- 因为步骤3出现异常, 事务回滚. 步骤2和3使⽤同⼀个事务, 所以步骤2的数据也回滚了.
2.3.2REQUIRES_NEW(新建事务)
将上述UserService 和LogService 中相关⽅法事务传播机制改为Propagation.REQUIRES_NEW
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}
运⾏程序, 发现⽤⼾数据插⼊成功了, ⽇志表数据插⼊失败.
LogService ⽅法中的事务不影响 UserService 中的事务
当我们不希望事务之间相互影响时, 可以使⽤该传播⾏为
2.3.3NEVER (不⽀持当前事务, 抛异常)
修改UserService 中对应⽅法的事务传播机制为
Propagation.
NEVER
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NEVER)
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}
程序执⾏报错, 没有数据插⼊
2.3.4NESTED(嵌套事务)
将上述UserService 和LogService 中相关⽅法事务传播机制改为
Propagation.
NESTED
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name,String op){
int a=10/0;
//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");
}
}
运⾏程序, 发现没有任何数据插⼊.
流程描述:
1.
Controller 中p1 ⽅法开始事务
2.
UserService
⽤⼾注册, 插⼊⼀条数据 (嵌套p1事务)
3.
LogService
记录操作⽇志, 插⼊⼀条数据(出现异常, 执⾏失败) (嵌套p1事务, 回滚当前事务, 数
据添加失败)
4.
由于是嵌套事务,
LogService
出现异常之后, 往上找调⽤它的⽅法和事务, 所以⽤⼾注册也失败
了
5.
最终结果是两个数据都没有添加
p1事务可以认为是⽗事务, 嵌套事务是⼦事务. ⽗事务出现异常, ⼦事务也会回滚, ⼦事务出现异常, 如果不进⾏处理, 也会导致⽗事务回滚.
NESTED和REQUIRED 有什么区别?
•
整个事务如果全部执⾏成功, ⼆者的结果是⼀样的.
•
如果事务⼀部分执⾏成功, REQUIRED加⼊事务会导致整个事务全部回滚. NESTED嵌套事务可以实现局部回滚, 不会影响上⼀个⽅法中执⾏的结果.
嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有⼀个保存点(savepoint)的概念, 嵌套事务进⼊之后相当于新建了⼀个保存点, ⽽滚回时只回滚到当前保存点.
总结
1.
Spring中使⽤事务, 有两种⽅式: 编程式事务(⼿动操作)和声明式事务. 其中声明式事务使⽤较多,在⽅法上添加 @Transactional
就可以实现了
2.
通过
@Transactional(isolation = Isolation.SERIALIZABLE)
设置事务的隔离级
别. Spring 中的事务隔离级别有 5 种
3.
通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的事务传播级别有 7 种, 重点关注 REQUIRED
(默认值) 和
REQUIRES_NEW