PHP樂觀鎖結(jié)合事務(wù)扣除余額失敗:如何保證并發(fā)情況下余額正確扣除?

PHP樂觀鎖結(jié)合事務(wù)扣除余額失敗:如何保證并發(fā)情況下余額正確扣除?

php樂觀鎖與數(shù)據(jù)庫事務(wù)結(jié)合扣除余額:問題分析與解決方案

本文探討在PHP環(huán)境下,使用樂觀鎖和數(shù)據(jù)庫事務(wù)進(jìn)行余額扣除時(shí),如何避免并發(fā)問題導(dǎo)致余額扣除失敗或數(shù)據(jù)不一致的情況。 我們將分析錯(cuò)誤代碼,并提供正確的解決方案。

問題代碼分析及錯(cuò)誤原因:

以下代碼片段試圖通過樂觀鎖和事務(wù)保證并發(fā)環(huán)境下余額扣除的正確性,但存在缺陷:

立即學(xué)習(xí)PHP免費(fèi)學(xué)習(xí)筆記(深入)”;

錯(cuò)誤代碼片段一:

public function userbuy() {     $user = $this->getuser();      $oldmoney = $user['balance'];      $orderoffer = $this->getordermoney($orderid);      if($oldmoney < $orderoffer['price']) $this->error('賬戶余額不足');      //樂觀鎖方案 (錯(cuò)誤之處)     $newmoney = $oldmoney - $orderoffer['price'];      $newuser = smsuser::where(['id' => $user['id'],'balance' => $oldmoney])->find();     if(!$newuser) $this->error('用戶不存在');     //開啟數(shù)據(jù)庫事務(wù)     db::transaction(function () use($newuser,$orderid,$newmoney){         $newuser->balance = $newmoney;         $result = $newuser->save();         if(!$result) $this->error('保存余額失敗');           //創(chuàng)建訂單 code          //扣除庫存 code          //創(chuàng)建用戶余額變動(dòng)記錄 code             db::commit(); //提交事務(wù) (多余操作)             }); }

錯(cuò)誤原因:

  1. 樂觀鎖位置錯(cuò)誤: find() 方法在事務(wù)外部執(zhí)行。多個(gè)并發(fā)請求都獲取到相同的 $oldmoney 值。 只有第一個(gè)請求的 save() 操作成功,后續(xù)請求由于 balance 不再等于 $oldmoney 而失敗。 樂觀鎖的條件判斷應(yīng)該在 update 語句中進(jìn)行。

  2. db::commit() 多余: db::transaction() 方法本身會(huì)自動(dòng)提交事務(wù),除非發(fā)生異常回滾。手動(dòng)調(diào)用 db::commit() 冗余且可能掩蓋錯(cuò)誤。

錯(cuò)誤代碼片段二:

public function userbuy() {     // ... (代碼同上,省略部分) ...      //樂觀鎖方案 (錯(cuò)誤之處)     $newUser->balance = $newMoney;     $result = $newUser->save();     if(!$result) $this->error('保存余額失敗');      //開啟數(shù)據(jù)庫事務(wù) (錯(cuò)誤之處)     Db::transaction(function () use(){          //創(chuàng)建訂單 code          //扣除庫存 code          //創(chuàng)建用戶余額變動(dòng)記錄 code             Db::commit(); //提交事務(wù) (多余操作)              }); }

錯(cuò)誤原因:

余額更新操作在事務(wù)外部執(zhí)行。如果后續(xù)操作(創(chuàng)建訂單、扣除庫存等)失敗,余額已經(jīng)更新,導(dǎo)致數(shù)據(jù)不一致。

正確解決方案:

應(yīng)該將樂觀鎖的條件判斷放在數(shù)據(jù)庫 update 語句中,并將所有需要保證原子性的操作都包含在同一個(gè)事務(wù)中。

正確代碼:

public function userbuy() {     $user = $this->getuser();     $oldmoney = $user['balance'];     $orderoffer = $this->getordermoney($orderid);     if ($oldmoney < $orderoffer['price']) $this->error('賬戶余額不足');      $newmoney = $oldmoney - $orderoffer['price'];      // 使用數(shù)據(jù)庫的樂觀鎖機(jī)制 (例如mysql)     $affectedRows = smsuser::where('id', $user['id'])         ->where('balance', $oldmoney)         ->update(['balance' => $newmoney]);      if ($affectedRows === 0) {         $this->error('余額更新失敗,可能存在并發(fā)沖突'); // 樂觀鎖失敗         return;     }      // 開啟事務(wù),包含所有后續(xù)操作     db::transaction(function () use ($orderid, $newmoney, $user) {         // 創(chuàng)建訂單 code         // 扣除庫存 code         // 創(chuàng)建用戶余額變動(dòng)記錄 code  (確保這些操作也包含在事務(wù)中)     }); }

這個(gè)解決方案將余額更新和后續(xù)操作都放在同一個(gè)事務(wù)中,并使用數(shù)據(jù)庫提供的樂觀鎖機(jī)制來保證數(shù)據(jù)一致性。如果任何操作失敗,事務(wù)會(huì)自動(dòng)回滾,確保數(shù)據(jù)安全。 避免了手動(dòng)提交事務(wù),簡化了代碼并提高了可讀性。 如果 update 語句影響的行數(shù)為0,則表示樂觀鎖失敗,需要處理并發(fā)沖突。 可以使用重試機(jī)制或其他策略處理此類情況。 具體的樂觀鎖實(shí)現(xiàn)方式取決于使用的數(shù)據(jù)庫系統(tǒng)。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊8 分享