所谓的大事务就是耗时比较长的事务。
Spring有两种方式实现事务,分别是编程式和声明式两种。
不手动开启事务,mysql 默认自动提交事务,一条语句执行完自动提交。
一、大事务产生的原因
- 操作的数据比较多
- 调用了 rpc 方法
- 有其他非 DB 的耗时操作
- 大量的锁竞争
- 执行了比较耗时的计算
二、大事务造成的影响
- 并发情况下,数据库连接池容易被撑爆
- 锁定太多的数据,造成大量的阻塞和锁超时
- 执行时间长,容易造成主从延迟
- 回滚所需要的时间比较长
- undo log日志膨胀,不仅增加了存储的空间,而且可能降低查询的性能
最主要的影响数据库连接池容易被撑爆,导致大量线程等待,造成请求无响应或请求超时。
三、大事务优化方案
1、使用编程式事务
在实际项目开发中,有多个写请求就需要用到事务,我们在业务方法加上@Transactional
注解开启事务功能,这是非常普遍的做法,它被称为声明式事务
。
部分代码如下:
@Transactional
public void save(User user) {
//doSameThing...
}
然而,我要说的第一条是:少用@Transactional注解。
为什么?
- 我们知道@Transactional注解是通过spring的aop起作用的,但是如果使用不当,事务功能可能会失效。如果恰巧你经验不足,这种问题不太好排查。
- @Transactional 注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。
那我们该怎么办呢?
可以使用编程式事务
,在spring项目中使用TransactionTemplate
类的对象,手动执行事务。
部分代码如下:
@Autowired
private TransactionTemplate transactionTemplate;
public void save() {
transactionTemplate.execute((status) -> {
userMapper.insertSelective(user);
goodsMapper.insert(goods);
return Boolean.TRUE;
});
}
如果是在说在编程式事务事务中,需要回滚,我们可以这样操作
public void save() {
transactionTemplate.execute((status) -> {
userMapper.insertSelective(user);
//直接抛异常,就会自动回滚
Assert.error("业务逻辑没通过,直接报错");
goodsMapper.insert(goods);
return Boolean.TRUE;
});
}
从上面的代码中可以看出,使用TransactionTemplate的编程式事务功能自己灵活控制事务的范围,是避免大事务问题的首选办法。
当然,我说少使用@Transactional
注解开启事务,并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务
也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。
2、将查询(select)方法放到事务外