使用的是 "illuminate/database": "^9.3"
最近業(yè)務(wù)上線,發(fā)現(xiàn)經(jīng)常產(chǎn)生死鎖。
排查過sql語句,并沒有發(fā)現(xiàn)什么異常
隔離級別是Read committed,
執(zhí)行update的時候也是根據(jù)主鍵ID進行的。
隔三差五的就會導(dǎo)致mysql死鎖,通過KILL把死鎖的KILL了也沒用,后續(xù)繼續(xù)執(zhí)行事務(wù)也會導(dǎo)致死鎖。
解決方式是只能php start.php reload 然後就正常了。
請問有遇過類似情況的嗎?
問題已經(jīng)解決,感謝各位。
順帶寫清楚原因方便後者參考。
$dbCheck = $request->post('dbCheck',false);
try{
Db::beginTransaction();
$rs = Db::table('tbl_product ')->where('p_id','=',12345)->select('planID')->first();
if(!$rs) throw new Exception('not found');
$rs = Db::table('tbl_rateList')->where([
'planID'=>$rs->planID,
'bookDate'=>date('Y-m-d')
])->select('id,Price')->first();
if(!$rs){
Db::table('tbl_rateList')->insert(.....);
}else{
Db::table('tbl_rateList'))->where([
'planID'=>$rs->planID,
'bookDate'=>date('Y-m-d')
])->update(.....);
}
if($dbCheck){
Db::rollBack();
return json();
}
Db::commit();
return json([....])
}catch(\Exception $e)
{
Db::rollBack();
return json([....])
}
$dbCheck這個是後加的功能,實際代碼中有價格核驗,比底價低了就進行二次確認(rèn)。
在單線程下進行,是沒有問題的,但是只要符合這三個條件,那麼就會產(chǎn)生死鎖:
1、3個或以上的INSERT操作
2、表中存在UNIQUE類型的唯一索引
3、第一個INSERT操作產(chǎn)生了rollBack
前端是進行批量修改的時候,會傳遞DBConfirm參數(shù),而單條記錄的修改,是不會傳遞這個參數(shù)。
如果用戶首先進行批量修改,傳遞了dbConfirm參數(shù),同時其他用戶也進行批量/單條記錄的修改,此時會產(chǎn)生3個INSERT的transaction。
在SQL中抽象的流程為:
事務(wù)1 獲取了X鎖,並且插入成功;
事務(wù)2 嘗試插入,檢查UNIQUE索引,獲取S鎖 但失敗,繼續(xù)等待事務(wù)1
事務(wù)3 嘗試插入,檢查UNIQUE索引,獲取S鎖 但失敗,繼續(xù)等待事務(wù)1
當(dāng)事務(wù)1 進行回滾,事務(wù)2和事務(wù)3 此時能獲取S鎖,檢查重複值之後執(zhí)行插入申請X鎖,但由於事務(wù)2和3都已經(jīng)獲取了S鎖,所以導(dǎo)致X鎖獲取失敗兩個事務(wù)進入死鎖狀態(tài)。
增加多一個接口,專門處理價格檢測,不通過事務(wù)回滾來進行。
我也是第一次遇見這個問題,最終還是通過百度和對mysql的error log還原了整個事件。
自己的不足在於太過先入為主,總覺得是update導(dǎo)致的死鎖,哪知道是特定環(huán)境下insert導(dǎo)致的死鎖。(從第一次發(fā)生,把一開始通過UNIQUE KEY進行update操作改為 通過主鍵ID進行update操作)包括再這裡提問,我都是傾向於問題存在於 多線程 + UPDATE。
這個我清楚,所以排查過SQL語句,我是對A表進行操作。
偽代碼:
try{
Db::beginTransaction();
$rs = Db::table('tbl_product ')->where('p_id','=',12345)->select('planID')->first();
if(!$rs) throw new Exception('not found');
$rs = Db::table('tbl_rateList')->where([
'planID'=>$rs->planID,
'bookDate'=>date('Y-m-d')
])->select('id')->first();
if(!$rs){
Db::table('tbl_rateList')->insert(.....);
}else{
Db::table('tbl_rateList'))->where([
'planID'=>$rs->planID,
'bookDate'=>date('Y-m-d')
])->update(.....);
}
Db::commit();
return json([....])
}catch(\Exception $e)
{
Db::rollBack();
return json([....])
}
但是情況也是一樣。
異常最好catch (\Throwable $e)不要用catch(\Exception $e)。
catch \Throwable 可以捕獲任何異常和Error錯誤,catch \Exception 只能捕獲異常,不能捕獲Error