赞
踩
记一次Mysql异常:
MySQLTransactionRollbackException:lock wait timeout exceeded;try restarting transaction
排查方式:
去掉spring的事务再执行,如果不报这个错误,证明就是事务引起的;
产生问题的伪代码如下,可以看出问题产生的原因:
- //controller中调用service的方法,为methodA开启了一个事务,假设叫 “事务1”
- @Controller
- public class AController{
-
- public void a(){
- aService.methodA();
- }
- }
-
- //methodA 中删除B表数据,
- public class AService{
-
- @Transaction
- public void methodA(){
- /*
- 删除B表中的数据,"事务1" 已经获取了B表的写锁,
- 但是注意:"事务1" 删除完数据(methodB()执行完后),并没有提交 “事务1”, 问题就出在这里
- */
- bService.methodB();
-
- /*
- 开启一个新线程调用methodC, 由于methodC处于另一个线程中,而spring的事务是和线程绑定的,
- 所以spring会为methodC开启另一个事务,假设叫 “事务2”
- */
- Thread thread = new Thread(new Runnable(){
- public void run(){
- /*
- “事务2”中也要对B进行写操作,即也要获取B表的写锁,但此时写锁还被 “事务1”持有,
- 因此methodC 无法执行,一直卡住直到超时。
- 超时时间由变量 “innodb_lock_wait_timeout”进行配置,可以通过
- show varibales like 'innodb_lock_wait_timeout' 进行查看,单位为秒
- */
- cService.methodC();
- }
- })
- thread.start();
-
- //阻塞主线程直到
- thread.join();
- }
- }
-
- public class BService{
- @Transaction
- public void methodB(){
- //删除B表中的数据
- 执行 delete from b where .....
- }
- }
-
- public class CService{
- @Transaction
- public void methodC(){
- //修改 B表中的数据
- 执行 update b set .....
- }
- }
仔细看上面代码中的注释,分析过程:
① 主线程开启了一个事务1,对b表进行写操作,写完之后事务1未提交;
② 主线程开启子线程,且 子线程不执行完,主线程一直挂起(即join的作用)
③ spring的事务是通过线程进行传递的,子线程检测不到已经开启了事务1,会开启新事务,即事务2,准备对b表进行写操作
④ 由于b表写锁还被 事务1持有,所以事务2无法获取b表写锁,事务2一直等待,直到超时
总结就是:事务存在了嵌套(两个线程各开启了一个事务),外层事务对b表进行写操作,未释放锁,内层事务也要获取写锁操作b表,无法获取,一直等待。
事务1 开始
执行 delete from b ....... ,持有b表写锁
事务2开始
执行 update b set ...... , 获取b表写锁超时
事务2结束
事务1 结束
解决办法目前想到两种:
不用多线程,这样就不会开启事务2,就不会造成锁等待
如果你的业务场景允许 事务1 先提交,那么先提交事务1即可,即将 BService的传播途径改为Propogation.REQUIRES_NEW,表示处于一个新事务中,methodB()结束,事务1就提交了,事务2就可以正常获取b表写锁:
- public class BService{
- @Transaction(propogation=Propogation.REQUIRES_NEW)
- public void methodB(){
- //删除B表中的数据
- 执行 delete from b where .....
- }
- }
如果有其他更好方式,请留言告知。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。