最近在我们项目中,出现了数据库死锁问题,这里简单记录下分析和解决的过程。
一、原因分析
1.a系统调用c系统的接口进行解析nameList时候,c系统会返回一个taskId,然后在a系统中根据taskId更新fixterm_task_t表。由于接口只允许单个name调用,所以得for循环调用。可能出现20条数据,前10条c系统已经完成了然后给a系统发消息进行后续的步骤。但是a系统还在for循环中进行调用接口执行后10条数据,所以后10条数据的taskId还未更新,还是默认值0。
public void sendTask(List<String> nameList){
taskSerice.noTxUpdateById(name);
}
2.同时c系统发消息到a系统的消费端接收的时候,在消费消息处理时候会去数据库里面查询出fixterm_task_t这一流程单号所有的数据,然后去对这一批次数据加锁更新数据。加锁的时候是按照name排序加锁的。但是更新的时候,使用的是taskId进行更新,所以存在了在这一批次中还有taskId为0的数据,根据taskId=0,更新的就不止1条了,可能会锁住多行。如果多条消息同时接收到,然后同时锁住taskId=0的行,数据库内可能就会存在锁竞争的情况。
public void handkeMessage(String msg){
fixTermService.lockAndUpdateTask(msg);
}
public boolean lockAndUpdateTask(TaskResult taskResult){
// 根据taskResult查询得到taskList
// 排序
taskDao.sort(Comparator.comparing(TaskResult.getName));
// 锁表 update fixterm_task_t set last_update_date = sysdate where name= #{task.name}
taskDao.lockTaskList(taskList);
// 更新当前name状态
int rows = taskDao.updateTaskStatus(TaskResult,"READY");
// 避免集群情况发生,如果一台机器更新成功,其它机器将更新失败
if(rows == 0){
return false;
}
// 查询当前那么状态,判断一个流程下name的状态是否全部更新完成
}
二、解决方案
这里在锁表的时候,不能再根据name了,上面已经分析过了会存在死锁问题。由于已经根据taskResult查询得到taskList了且taskList中包含主键id,所以锁表语句根据id进行更新。
-- 锁表
update fixterm_task_t set last_update_date = sysdate where id= #{task.id}