【问题标题】:Parallel transactions on database lead to incorrect result, laravel数据库上的并行事务导致不正确的结果,laravel
【发布时间】:2021-08-08 10:41:07
【问题描述】:

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

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.
}

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

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,具体取决于最后更新用户钱包余额的操作。

现在,我的问题是,我该如何处理我的钱包系统以避免这个臭名昭著的问题。我会非常感谢你们。

您可以建议更好的方式来表达问题。

【问题讨论】:

  • 你看过数据库事务吗?
  • 他们能帮助解决这个问题吗? @Aless55 让我看看。如果您有资源丰富的链接,请放在这里,我将不胜感激。
  • 事实证明数据库事务在这种情况下无济于事。这是因为操作(钱包存款和服务收费)是两个完全不同的实例。服务收费由系统触发,钱包充值由用户触发。

标签: php mysql laravel


【解决方案1】:

您必须使用原始 mysql 查询锁定表的行,我不知道 eloquent 是否具有该类型查询的某些功能,但您可以深入研究。 看看这篇文章,看清楚了https://medium.com/@ibraheemabukaff/locking-rows-in-mysql-e84fd3bbb8cd

【讨论】:

    【解决方案2】:

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

    $params = ['fee' => 30, 'user_id' => $request->user_id];
    DB::statement('UPDATE users SET wallet_balance=wallet_balance-:fee WHERE id=:user_id', $params);
    

    或锁定记录以进行更新:

    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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-06-12
      • 2014-12-01
      • 2019-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-28
      相关资源
      最近更新 更多