记项目中一次lock wait timeout exceeded try restarting transaction问题
这周回顾研究了一下上周修数据时碰到的一个事务锁超时问题,发生的业务场景是:在一个事务内对数据进行先删除后插入的操作,结果执行代码时发现部分请求能请求成功,部分请求报事务锁超时问题,当初临时紧急的解决方案是把删除和插入单独放入两个事务去执行,虽然问题解决了,但是根本的原因还未找到,所以这周抽空排查了一下产生该问题的原因:
一开始排查时以为是在同一个事务中进行了两个数据库的操作,导致了数据库获取锁超时的问题,后面把两个库的操作相互隔离起来后还是会发生该问题,
后面排除是两数据库的原因后,考虑是不是删除和插入的数据有共同的唯一索引的问题导致的,然后我把两条sql提取出来在mysql客户端单独开启事务去执行,结果发现成功了。这排除了共同唯一索引导致该问题的产生,但是为什么在代码中同一事务中提交却会产生锁超时问题,而在客户端显示开启事务却没有这个问题呢?考虑过会不会是Spring事务管理做了啥处理导致的该问题?然后跟进事务锁超时的线程到Spring底层一直跟进到调用mysql驱动execut方法后,发现确实是调用mysql数据库超时没有返回导致的问题,这就排除了spring事务的原因了。
那只可能是mysql这边确实是有数据库锁等待超时的问题了,后面我们把程序断点到超时重试获取事务锁的代码,通过查询mysql所有事务和等待获取锁的事务,惊奇的发现,在mysql事务中发起了两个事务,并且一个insert的事务在等待另外一个事务释放锁。这就是之前我们在mysql客户端显示开启事务时为什么是成功,而在程序中开启事务执行却失败的原因了,因为程序中产生了两个事务,第二个insert行为的事务要获取第一个事务占有的唯一索引的行锁才能执行,而程序中占有锁的事务必须要等inser执行成功才能提交事务,这就导致占有锁的事务(delete操作)和insert互相等待导致了事务锁等待超时的问题产生了。
到这里已经快接近真相了,一个transaction事务方法中为什么会产生两个事务呢?后续继续排查原因,发现insert操作的mapper接口被两相同配置的数据源datasources同时扫描加载了,这就导致了在执行insert的时候两个数据源会开启两个事务,其中编号1(姑且这么叫吧)的事务是跟delete操作同一个事务,所以没有锁问题,另外一个是新开启的事务编号2(姑且这么叫吧),编号2的insert事务需要获取编号1的事务占有的行锁,编号1的事务又需要insert操作完成才能执行事务提交,这就导致了前面说的编号2的事务获取事务锁超时的问题产生了
问题原因找到了,后续解决方案就简单了,1、避免重复的数据源配置,这会产生刚说的产生多个事务的问题,2、尽量避免同一packeg被多个数据源加载。
网友评论