1,现象简述:
项目上线,serviceA.methodA()中调用serviceB.methodB(String schoolId)的方法。
serviceA.methodA()核心业务更新学校信息记录,核心sql:
update table_school set xx=xx where id=xxx;
serviceB.methodB(String schoolId)核心业务是将学校update记录加入数据库中,将update的信息异步同步给其他服务。再入库之前,要去table_school表中查询所有is_sync=1的学校id,根据此字段判断这个学校是否需要同步。is_sync=0,则不入库。
List<String> schoolIds = schoolDAO.findSchoolIds(); //select id from table_school where is_sync=1;
if(!schoolIds.contains(schoolId)){
continue; //不处理
}
//入库
结果:在获取schoolIds 时候,程序卡住。所有需要查询table_school表的sql全部阻塞。程序处于不可用状态。
2,分析原因:
当时通过运维查看数据库进程,了解到:1,数据库阻塞;2,table_school阻塞导致;3通过debug信息定位到具体问题dao,确认问题sql(种种原因,运维无法定位到具体sql)是 select id from table_school where is_sync=1 。
最重要的是当时只知道是阻塞。没有信息表明是发生了死锁。而且因为经验不足,数据库性能本身就有问题等各种因素。也没往这个方向考虑。一直怀疑是数据库性能和一些未知的慢sql导致阻塞。(经验不足,考虑方向错的离谱=。=)。
3,解决问题:
涉及到两个service的方法。查看methodA(),methodB()的事务设置。
methodA()为PROPAGATION_REQUIRED,timeout_300
methodB()为PROPAGATION_NOT_SUPPORTED
这就是问题的关键了,methodA()方法在新建的事务中执行,调用methodB()时,methodB()将methodA()的事务挂起,并且以非事务执行。这样导致methodA()的update school一直没提交,methodB()又去select school,造成了死锁(互斥锁)。因为methodA()的事务被挂起,methodB()没有事务,也不会发生事务超时。日志只能看到很久之后,dbserver close connection 的日志。无法定位程序问题。
最后将schoolIds 的获取放到methodA()中,并将schoolIds作为参数传入methodB()中解决。
4,总结:
菜是原罪!
其实这个问题总的来说,并不复杂。只要能定位到当时数据库发生死锁,查到造成死锁的sql,就能很容易想到互斥锁,查看事务。主要时第一次遇到这种问题,经验不足,真的想不到这层原因。
5 ,收获:
spring事务传播机制了解的更深了。
数据库共享锁(s),互斥锁(x)。
查询数据库死锁,定位死锁sql的方法。
网友评论