导致长事务原因
长事务是因为在事务中作了大量耗时的操作,如调用第三方,保存过多的数据,执行了与事务不相关的操作。
@Service
public class UserService {
@Transactional
public void updateUser(){
// 调用第三方(第三方耗时)
// 执行更新sql
this.updateUserById()
}
}
上述方法就是在@Transactional的方法中,执行业务逻辑时,有耗时操作,@Transactional在调用该方法时,从数据库连接池中获取到了连接,且当前连接一直占用
长事务产生原因
- 大量锁竞争
- 事务中耗时操作
- 调用第三方(请求设置超时时间60s)
导致的问题
- 数据库连接池被占满,应用无法获取连接资源;
- 锁定太多的数据,造成大量的阻塞和锁超时;
数据库连接池被占满,应用无法获取连接资源,根据上一篇分析@Transational原理知道调用了updateUser方法时,代理会获取到connection连接,并开启事务,没有提交事务时,会一直占用该连接,大量调用时,就容易导致数据库连接池占满
锁太多数据,是因为更新操作操作很多数据时,这些数据获得了行锁,这些行锁在提交事务后释放掉,如果没有被释放,其他需要获取写入锁时,都得等待。从而导致阻塞和锁超时
如何避免长事务
解决长事务的宗旨就是对事务方法进行拆分,尽量让事务变小,变快,减小事务的颗粒度。
- 在一个事务里面,避免一次处理太多数据;
- 在一个事务里面,尽量避免不必要的查询;
- 在一个事务里面,避免耗时太多的操作,造成事务超时。一些非DB的操作,比如rpc调用,消息队列的操作尽量放到事务之外操作;尽量避免在事务中调用第三方
拆分
声明式事务中(@Transactional),将一些查询放在事务外面,A类的a方法查询判断逻辑,然后a调用B类的事务b方法进行更新操作。
@Service
public class A {
@Autowired
private B b;
public voie test(){
// 查询
this.findUser();
b.update()
}
}
@Service
public class B {
@Transactional
public void update(){
// 执行sql
}
}
编程式事务
基于底层的API,开发者在代码中手动的管理事务的开启、提交、回滚等操作。在spring项目中可以使用TransactionTemplate类的对象,手动控制事务。
@Autowired
private TransactionTemplate transactionTemplate;
//...
public void save(RequestBill requestBill) {
transactionTemplate.execute(transactionStatus -> {
requestBillDao.save(requestBill);
//保存明细表
requestDetailDao.save(requestBill.getDetail());
return Boolean.TRUE;
});
}
此种方式也是阿里巴巴java开发手册中推荐的使用方法,此种需要多写一些代码。但是此种方式也是无法避免长事务,也是需要开发者注意不能再事务中处理耗时任务。
事务后处理
@Transactional(rollbackFor = Exception.class)
public Boolean inviteUser(..) {
userService.save(..);
// 事务提交后再处理
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 调用第三方
}
});
TransactionSynchronizationManager.registerSynchronization注册一个处理器,在事物提交后处理业务。
事务提交后处理,并不能保证第三方一定能成功,这就可能导致事务不一致。如果不是强一致的事物时,通过人工兜底的方式来处理事务不一致。人工补偿的方式在程序设计中,也是很常见的,利用中间件rocketmq来解决事务不一致时,其实也需要人工补偿的方案的。
总结
在编码过程中,需要考虑到在事务中避免做耗时的操作。如果在事务中调用第三方,则需要设置请求相对较短的超时时间。