开启事务致数据库死锁
首先描述一下问题出现的业务场景。项目中有一个模块是做数据处理的,该模块的主体是方案,每个方案分成三个数据库表,基础表存储方案的基础信息,参数表存储方案的输入参数信息,结果表存储方案处理结果。业务逻辑如下,在前端页面输入方案的基础信息和参数信息,发送这些数据到后台应用,后台应用将方案信息落库成三个表的数据,然后再组装参数信息发送给下游的微服务,微服务根据输入参数等信息,从其他系统获取数据,处理成最终参数集(参数集存储在ES);微服务处理完数据后,会将整个方案的数据集等参数发送到最终的方案计算模块(外部系统),然后变换方案的结果状态。最后页面查询展示方案结果,调用计算模块的查询结果接口并更新当前方案结果。
在这个过程中微服务处理数据时可能存在数据出错或缺失,这个时候更新方案为出错状态,出错状态时前端可以再次编辑方案参数,再次发起请求,请求会先删除原来的老方案数据库中的数据,然后再次写入新方案数据。
再次请求和新建请求之间差距就是再次请求会多一次删除老数据的操作。而我的问题就出现在再次请求这里,新建请求写入数据库正常,但是再次请求时,删除数据和新建数据一直失败,因为微服务在处理数据时,是需要查询方案信息的,如果查询不到方案信息,则会报错。
经过很长时间的查找,在同事的火眼金睛下,一眼看到了原来再次请求的接口上打了一个事务注解@Transactional
,因为这个接口之前是同事写的,所以一直没注意到这个注解符号,也正式因为开启了这个注解,导致了一些的问题。
问题解析,注解打在再次请求的地方,首先从数据库中删除老数据,然后插入新数据,再发起请求到微服务,微服务从数据库中查询新数据,因为这是一个事务,还没提交,所以微服务查询新数据库为空,接口返回报错,后台就回滚删除老数据和插入新数据的操作。导致了整个链路的死锁。
在此也得出,@Transactional
这个注解虽然很方便的为我们开启事务,但是需要分场合,逻辑处理复杂的,链路很长的、跨请求的事务,尽量不要使用这个注解,因为很容易导致死锁至数据库链接耗尽和长时间锁表导致系统崩溃。数据库事务应该尽量简短。
以上就是自己在这次事务注解中总结的经验教训,在以后的工作中应该尽量避免这样的操作。也对这样的操作检查更加仔细。