首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >数据库上的并行事务导致错误的结果,laravel

数据库上的并行事务导致错误的结果,laravel
EN

Stack Overflow用户
提问于 2021-05-19 15:47:52
回答 2查看 168关注 0票数 1

我的应用程序是用php/laravel和MySQL数据库实现的。在这个系统中,用户可以将钱存入钱包。我在users表中有一个wallet_balance列。当用户将资金存入他们的钱包时,我将该列更新如下:

代码语言:javascript
复制
public function topup(Request $request)
{
    $user = auth()->user();
    $deposit_amount = $request->deposit_amount;
    //e.g.
    //$deposit_amount = 100
    //$user->wallet_balance = 50
    $user->wallet_balance = $user->wallet_balance + $deposit_amount;
    $user->save();
    // After saving I expect $user->wallet_balance to be 150 which works perfectly.
}

有些服务从用户的钱包中扣除费用(在另一个功能中)。例如:

代码语言:javascript
复制
public function chargeService(Request $request)
{
    $user = User::findOrFail($request->user_id);
    $service_fee = $request->service_fee;
    //$service_fee = 30
    //$user->wallet_balance = 150
    $user->wallet_balance = $user->wallet_balance - $service_fee;
    $user->save();
    // After saving I expect $user->wallet_balance to be 120 which works perfectly.
}

完成两次交易后,用户的钱包余额应该是120。但是,在极少数情况下,这两个事务可能会同时发生。这意味着在存款交易中,初始钱包余额是50,对于服务费交易,初始钱包余额是50 (因为他们在两者中的任何一个更新钱包余额之前同时查询数据库)。危险就在这里。第一个事务的钱包余额为150 (50 + 100),第二个事务的钱包余额为20 (50 - 30)。这使得用户的钱包余额为150或20,这取决于哪个操作最后更新用户的钱包余额。

现在,我的问题是,我如何才能接近我的钱包系统,以避免这个臭名昭著的问题。我会很感谢你们的。

你可以建议一种更好的方式来表达这个问题。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-05-19 20:54:17

您面临的主要问题是PHP中加载的用户状态不能反映数据库中的内容。您应该对数据库执行查询:

代码语言:javascript
复制
$params = ['fee' => 30, 'user_id' => $request->user_id];
DB::statement('UPDATE users SET wallet_balance=wallet_balance-:fee WHERE id=:user_id', $params);

或锁定记录进行更新:

代码语言:javascript
复制
try {
    DB::beginTransaction();
    // it is important to get the state from the 
    // database at the time of locking
    $user = User::query()->lockForUpdate()->findOrFail($request->user_id);
    $user->wallet_balance = $user->wallet_balance - $service_fee;
    $user->save();
    DB::commit();
} catch (Throwable $e) {
    DB::rollBack();
}
票数 2
EN

Stack Overflow用户

发布于 2021-05-19 19:08:42

你必须使用原始的mysql查询来锁定表格的行,我不知道eloquent是否有针对这种类型的查询的一些功能,但你可以深入挖掘。看一看这篇文章,对https://medium.com/@ibraheemabukaff/locking-rows-in-mysql-e84fd3bbb8cd有一个清晰的了解

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67598992

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档