记一次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 .....
}
}
如果有其他更好方式,请留言告知。