1.问题的现象
事情的起因:有天晚上,突然数据库开始预警,然后所有功能运行的特别慢,页面数据加载不出来,经过紧急处理后,定位到是@Transactional导致的大事务,拖垮了数据库,导致其他的功能也无法运行。
下面这段代码看起来是非常普通的一段代码
queryDTable1() 查询表1
outerServiceA() 请求外部服务A
outerServiceB() 请求外部服务B
outerServiceC() 请求外部服务C
updateTable1()更新表1
updateTable2()更新表2
2.问题的定位
看看问题是怎么出现的:
由于使用了@Transactional注解,进入方法就开启了事务。
外部系统开始出现问题,接口调用缓慢或者超时,请求就卡在outerServiceA/B/C 上,这时候事务无法及时释放,叠加业务高峰期,导致数据库出现了大量的锁。
数据库被大量的事务拖垮,其他业务开始受到影响,表现为整个系统变的缓慢,页面数据加载不出来。
@Service
public class TransactionalService {
@Transactional(rollbackFor = Exception.class)
public Boolean service(){
//1.查询表1
queryDTable1();
//2.请求外部服务A
outerServiceA();
//3.请求外部服务B
outerServiceB();
//4.请求外部服务C
outerServiceC();
//5.更新表1
updateTable1();
//6.更新表2
updateTable2();
return true;
}
}
3.问题的解决
不要使用@Tansactional注解式事务,使用编程式事务,让事务尽可能的小。
删除@Transactional注解
queryTable1() 、outerServiceA()、outerServiceB()、outerServiceC()三个方法都不要放在事务中。
将updateTable1()和updateTable2()更新放到编程式事务里
如果更新失败,则回滚
就算外部服务A,B,C出问题超时,最多的超时报错,仅仅影响service方法中的逻辑,不会开启事务,也就不会影响数据库可以保证其他业务正常运行,有效的控制影响范围。
@Slf4j
@Service
public class TransactionalService_进化版本 {
@Resource
private TransactionTemplate transactionTemplate;
//不要用@Transactional事务了!!!!
// @Transactional(rollbackFor = Exception.class)
public Boolean service(){
//1.查询表1
queryTable1();
//2.请求外部服务A
outerServiceA();
//3.请求外部服务B
outerServiceB();
//4.请求外部服务C
outerServiceC();
//5.事务统一处理!!!
transactionTemplate.execute((transactionStatus)->{
try {
//事务尽可能尽可能的小
updateTable1();
updateTable2();
} catch (Exception e) {
transactionStatus.setRollbackOnly();
log.error("【service】update error!",e);
}
return transactionStatus;
});
return true;
}
}
4.经验教训
1.尽量少用,甚至不要用@Transactional注解,多使用编程式事务。
@Transactional是通过SpringAOP实现的,在某些情况下,事务会失效。
@Transactional一般加在方法上,事务粒度不好控制,这个方法里面可能有非常非常多的业务逻辑,比如读取缓存,调用其他的业务服务,甚至调用其他系统的方法,会导致大事务。使用@Transactional不好控制事务的力度(如果你方法拆分的非常好,可以使用)
2.事务中不要一次性更新太多数据。
比如在一个事物中一次性更新 1000条,这样会导致大量的数据库的行锁等待,其他的请求访问这条数据,会导致等待。
正确的做法是,分批更新,一次性更新50条,提交一次事务,再更新下一批。
3.真正需要加事务的地方加事务
并不是所有的数据库的操作都需要在一个事物中执行,只有需要保证数据完整性的数据,才需要在一个事物中。如果是非核心的动作,比如记录操作轨迹,这种完全没有必要和业务数据放在一个事物中。可以异步执行(@Async或者Spring Event异步)
4.事务中不要调用外部系统
事务中,不要调用其他的系统接口,也不要操作别的数据库/中间件(Redis,MQ,ES)等
事务就是事务,保持最小最单纯原则
5.事务中尽量不要做查询
事务中仅仅做新增,更新,删除操作,查询放到事务外面。