数据库不使用悲观锁导致问题的一种复现方式

网站访问量达到一定规模的时候,并发用户都在改数据的时候,可能会出现问题,比如一个活动限制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 协议》,转载必须注明作者和本文链接
大多数知识,不需要我们记住,只需要认知即可
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 1

lockForUpdate 还不悲观啊

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!