数据库不使用悲观锁导致问题的一种复现方式
网站访问量达到一定规模的时候,并发用户都在改数据的时候,可能会出现问题,比如一个活动限制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 协议》,转载必须注明作者和本文链接
lockForUpdate 还不悲观啊