[已解決]webman多进程导致mysql死锁

MarkGo

使用的是 "illuminate/database": "^9.3"

最近业务上线,发现经常产生死锁。
排查过sql语句,并没有发现什么异常

隔离级别是Read committed,
执行update的时候也是根据主键ID进行的。

隔三差五的就会导致mysql死锁,通过KILL把死锁的KILL了也没用,后续继续执行事务也会导致死锁。
解决方式是只能php start.php reload 然後就正常了。

请问有遇过类似情况的吗?

問題已經解決,感謝各位。
順帶寫清楚原因方便後者參考。

$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這個是後加的功能,實際代碼中有價格核驗,比底價低了就進行二次確認。
在單線程下進行,是沒有問題的,但是只要符合這三個條件,那麼就會產生死鎖:

1、3個或以上的INSERT操作
2、表中存在UNIQUE類型的唯一索引
3、第一個INSERT操作產生了rollBack

還原過程

前端是進行批量修改的時候,會傳遞DBConfirm參數,而單條記錄的修改,是不會傳遞這個參數。
如果用戶首先進行批量修改,傳遞了dbConfirm參數,同時其他用戶也進行批量/單條記錄的修改,此時會產生3個INSERT的transaction。

在SQL中抽象的流程為:
事務1 獲取了X鎖,並且插入成功;
事務2 嘗試插入,檢查UNIQUE索引,獲取S鎖 但失敗,繼續等待事務1
事務3 嘗試插入,檢查UNIQUE索引,獲取S鎖 但失敗,繼續等待事務1
當事務1 進行回滾,事務2和事務3 此時能獲取S鎖,檢查重複值之後執行插入申請X鎖,但由於事務2和3都已經獲取了S鎖,所以導致X鎖獲取失敗兩個事務進入死鎖狀態。

解決方式

增加多一個接口,專門處理價格檢測,不通過事務回滾來進行。

彎路

我也是第一次遇見這個問題,最終還是通過百度和對mysql的error log還原了整個事件。
自己的不足在於太過先入為主,總覺得是update導致的死鎖,哪知道是特定環境下insert導致的死鎖。(從第一次發生,把一開始通過UNIQUE KEY進行update操作改為 通過主鍵ID進行update操作)包括再這裡提問,我都是傾向於問題存在於 多線程 + UPDATE。

1514 4 1
4个回答

胡桃

都什么年代,还在用传统事务。

胡桃

不用猜都知道你的代码有问题,建议仔细检查代码排查出问题,要么改成可串行化。

  • MarkGo 2022-05-30

    runtime/logs 下的都查了,没发现异常;代码有问题不至于隔三差五才死锁吧?而且代码异常的话抛出错误后执行的rollback,也不会导致数据库死锁啊。现在webman是没有任何异常,是数据库产生死锁。至于mysql的日志也查了,之前是通过索引进行更新记录,后来修改为根据主键ID更新记录。

Tinywan

截图

  • MarkGo 2022-05-30

    这个我清楚,所以排查过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([....])
    }

    但是情況也是一樣。

  • xiuwang 2022-05-30

    异常最好catch (\Throwable $e)不要用catch(\Exception $e)。
    catch \Throwable 可以捕获任何异常和Error错误,catch \Exception 只能捕获异常,不能捕获Error

  • za2883632 2022-05-30

    就是代码问题。有事务,没有手动提交!,或者没回滚!!

  • 张顺飞 2022-05-30

    也是死锁,有什么好的办法解决吗

  • 张顺飞 2022-05-31

    https://www.workerman.net/q/3788 和这个问题差不多吧

gddd

一会简体 一会繁体,看的眼睛疼

  • 暂无评论
年代过于久远,无法发表回答
🔝