背景
最近在做一個訂單的釘釘審批功能杂彭,釘釘審批通過之后滥嘴,訂單更新審核狀態(tài)木蹬,然后添加一條入庫,并且更新入庫狀態(tài):
// 訂單審批通過
@Transactional(rollbackFor = Exception.class)
public void orderPass() {
// 更新訂單審核狀態(tài)
updateOrderAuditStatus(id);
// 添加入庫
addPutInStorage(id);
// 更新訂單入庫狀態(tài)
updateOrderStorageStatus(id);
}
其中的添加入庫
是遠(yuǎn)程ERP
入庫若皱,添加出庫之后更新出庫狀態(tài)
镊叁。因為ERP
可能因為庫存不足,會入庫失敗
走触。但此時審批流程已經(jīng)結(jié)束晦譬,不可能再發(fā)起一遍審批流程。當(dāng)添加入庫失敗
時訂單審核狀態(tài)
正常更新互广,添加入庫
和更新入庫狀態(tài)
失敗敛腌。這里的解決方案是:
拆分成兩個方法卧土,一個是更新訂單審核狀態(tài),另一個添加入庫和更新入庫狀態(tài)像樊。添加入庫和更新入庫狀態(tài)開啟一個事務(wù)尤莺,也就是添加
嵌套事務(wù) REQUIRES_NEW
,REQUIRES_NEW
表示無論是否有事務(wù),都會創(chuàng)建一個新的事務(wù)生棍。
修改后的代碼如下:
// 訂單審批通過
@Transactional(rollbackFor = Exception.class)
public void orderPass() {
// 更新訂單審核狀態(tài)
updateOrderAuditStatus(id);
try {
// 更新出庫
updatePutInStorage(id);
} catch (Exception e) {
System.out.println("更新出庫失敗");
}
}
// 更新出庫
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void updatePutInStorage(Long id) throws Exception{
// 添加入庫
addPutInStorage(id);
// 更新訂單入庫狀態(tài)
updateOrderStorageStatus(id);
System.out.println("更新出庫成功");
}
上面講代碼拆分成更新訂單審核狀態(tài)
和更新入庫
,其中更新入庫
報錯會被try catch
異常捕獲颤霎,不會影響到訂單審核狀態(tài)更新
。而添加入庫
和更新訂單入庫狀態(tài)
處于同一個事務(wù)下涂滴,要么同時成功友酱,要么同時失敗。上述問題也解決了柔纵。
然而運行結(jié)果:
com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
原因分析
鎖超時了缔杉,為什么會有鎖呢?主要是這里添加了REQUIRES_NEW
搁料。
- 外層事務(wù)對表的更新鎖住了表的行或详,外層事務(wù)還沒有提交,就調(diào)用了內(nèi)層事務(wù)
updatePutInStorage
,內(nèi)層事務(wù)調(diào)用了updatePutInStorage
加缘。 -
updatePutInStorage
需要更新訂單的入庫狀態(tài)
,此時外層事務(wù)鎖住了該表鸭叙,所以更新訂單的入庫狀態(tài)
無法更新觉啊。 -
更新訂單的入庫狀態(tài)
等待更新訂單的審核狀態(tài)
,而REQUIRES_NEW
又會讓更新訂單的審核狀態(tài)
等待更新訂單的入庫狀態(tài)
拣宏。造成相互等待,也就造成死鎖
杠人。
解決方案
死鎖:兩個線程為了保護(hù)兩個不同的共享資源而使用了兩個互斥鎖勋乾,那么這兩個互斥鎖應(yīng)用不當(dāng)?shù)臅r候,可能會造成兩個線程都在等待對方釋放鎖嗡善,在沒有外力的作用下辑莫,這些線程會一直相互等待,就沒辦法繼續(xù)運行罩引,這種情況就是發(fā)生了死鎖各吨。
上面鎖超時原因,就是死鎖的一種原因袁铐。所以需要把更新訂單審核狀態(tài)
方法放在最后:
// 訂單審批通過
@Transactional(rollbackFor = Exception.class)
public void orderPass() {
try {
// 更新出庫
updatePutInStorage(id);
} catch (Exception e) {
System.out.println("更新出庫失敗");
}
// 更新訂單審核狀態(tài)
updateOrderAuditStatus(id);
}
// 更新出庫
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void updatePutInStorage(Long id) throws Exception{
// 添加入庫
addPutInStorage(id);
// 更新訂單入庫狀態(tài)
updateOrderStorageStatus(id);
System.out.println("更新出庫成功");
}
總結(jié)
- 添加嵌套事務(wù)需要考慮到
死鎖
的問題揭蜒。 - 一個事務(wù)只有等全部方法執(zhí)行完畢之后才會提交事務(wù)。
- 含有嵌套的事務(wù)的更新剔桨,需要按照相同的順序更新屉更,不然可能會出現(xiàn)鎖相互等待的情況。
參考
業(yè)務(wù)上第一次遇到MySQL更新鎖表超時( Lock wait timeout exceeded; try restarting transaction)