很多时候,我们经常会在数据库事务的过程中做一些网络调用,比如Redis的更新、MQ的发送,RPC接口的调用等,其实这是非常不建议的。
@Transactional
public void order(OrderDTO orderDTO){
//本地数据库操作
orderServive.createOrder(orderDTO);
//远程外部调用
mqService.send(orderDTO);
}
这么做会带来几个问题:
1、增加事务持续时间:外部的网络调用会显著增加事务的执行时间,特别是网络延迟或外部服务响应慢的情况下。事务持续时间的增加会占用数据库资源更长时间,如锁定资源,这会影响数据库的并发处理能力和整体性能。
本来你操作完数据库就可以提交了,事务和数据库链接也就释放了。但是因为有外部调用,那么就会导致这个事务的时间边长,链接释放时间也变长。
2、死锁和资源竞争:长时间持有事务期间的锁资源会增加死锁的可能性,尤其是当系统负载高时。因为一旦在事务中开启了锁,比如select for update,需要事务提交或回滚后才能释放锁,这就会导致更多的锁冲突。
3、提高事务失败的风险:外部调用增加了事务失败的可能性。如果外部服务调用失败或超时,可能需要回滚事务,这不仅增加了复杂性,还可能导致数据不一致的问题。
这么做非常不好!
比如说在一些场景中,举个例子在用户下单过程,我们要在下单操作成功后发个MQ,但是因为你做了个事务,消息发送失败后,异常被捕获到,就会导致数据库回滚。大家想想,用户下单都成功了,就因为发MQ失败了,就把订单操作给回滚了,这根本就不合理啊!所以,这就会导致很多不在核心链路上的操作失败而导致核心链路回滚。
4、难以保证原子性:事务的一个关键属性是原子性,意味着事务内的操作要么全部成功,要么全部失败。当事务中包含外部调用时,很难保证事务和外部系统之间的原子性,因为外部系统的操作可能不易于回滚。
有这样一种情况,当我们调外部接口的时候,因为网络延迟, 我们拿到了一个timeoutException,这时候我们本地事务回滚了。
但是,远程操作虽然超时了,但是不代表他没执行,所以很可能他已经已经成功了!那么这就导致了数据的不一致!
所以,为了避免这些问题,通常建议的做法是: