資料庫不使用悲觀鎖導致問題的一種復現方式

tu6ge-php發表於2020-09-28

網站訪問量達到一定規模的時候,併發使用者都在改資料的時候,可能會出現問題,比如一個活動限制10個人參與,一個簡單的方式是在資料中設定一個欄位為 0,一個使用者參與了就 +1 ,但是當多個使用者同時參與活動的時候,有可能會多於10個使用者參與了活動,這樣的問題是由於資料庫沒有設定悲觀鎖導致的,但是如何在本地開發環境復現這種併發呢?

使用 guzzlehttp/guzzle 包,可以在本地模擬併發請求

use GuzzleHttp\Client;
// 併發請求程式碼
$client = new Client(['base_uri' => 'http://test.com/']);
$promises = [
    'a' => $client->getAsync('bonus.php?money=13'),
    'b' => $client->getAsync('bonus.php?money=23'),
    'c' => $client->getAsync('bonus.php?money=33'),
    'd' => $client->getAsync('bonus.php?money=43')
];
$results = Promise\unwrap($promises);
echo $results['a']->getBody()->getContents();
echo "\n";
echo $results['b']->getBody()->getContents();
echo "\n";
echo $results['c']->getBody()->getContents();
echo "\n";
echo $results['d']->getBody()->getContents();
echo "\n";

我們在 bonus.php 這個路由中,可以嘗試對資料庫修改

$rand = request()->get('money',0);
DB::beginTransaction();
try{
    $old_money = DB::table('test_table')->where('user_id','34')->lockForUpdate()->value('total_money');
    DB::table('test_table')->where('user_id','34')->update(['total_money'=>$rand]);
    $new_money = DB::table('test_table')->where('user_id','34')->value('total_money');
    DB::commit();
    $data = ['old_money'=>$old_money,'new_money'=>$new_money];
}catch (\Exception $e){
    $data = ['info'=>'rollback'];
    DB::rollBack();
}

dd($data);

可能的輸出結果是,money 由 23 變成 13,然後由 13 變成了 33,然後由 33 變成了43 ,如果不使用悲觀鎖,則資料庫記錄就會出現問題

本作品採用《CC 協議》,轉載必須註明作者和本文連結
大多數知識,不需要我們記住,只需要認知即可

相關文章