laravel数据库事务会锁定表吗?

我使用laravel5.5的数据库交易进行在线支付应用。我有一个company_account表来记录每笔付款(,,,)。。我需要在创建新记录时访问最后一条记录。所以我需要在交易时锁定表,用读写表锁,避免多次支付同时进行。typeamountcreate_atgross_incomegross_income

我已经参考了laravel的文档,但我不确定交易是否会锁定表。如果事务将锁定表,那么锁定类型是什么(读锁定,写锁定或两者兼而有之)?

DB::transaction(function () {
    // create company_account record

    // create use_account record
}, 5);

法典:

DB::transaction(function ($model) use($model) {
    $model = Model::find($order->product_id);
    $user = $model->user;

    // **update** use_account record
    try {
        $user_account = User_account::find($user->id);
    } catch (Exception $e){
        $user_account = new User_account;
        $user_account->user_id  = $user->id;
        $user_account->earnings = 0;
        $user_account->balance  = 0;
    }
    $user_account->earnings += $order->fee * self::USER_COMMISION_RATIO;
    $user_account->balance += $order->fee * self::USER_COMMISION_RATIO;
    $user_account->save();

    // **create** company_account record
    $old_tiger_account = Tiger_account::latest('id')->first();

    $tiger_account = new Tiger_account;
    $tiger_account->type = 'model';
    $tiger_account->order_id = $order->id;
    $tiger_account->user_id = $user->id;
    $tiger_account->profit = $order->fee;
    $tiger_account->payment = 0;
    $tiger_account->gross_income = $old_tiger_account-> gross_income + $order->fee;
    $tiger_account->save();
}, 5);

引用:
如何将参数传递给 Laravel DB::transaction()


答案 1

由于您要更新 2 个表,因此仍需要使用事务来保持更改同步。请考虑以下代码:

DB::transaction(function () {
    $model = Model::find($order->product_id);
    $user = $model->user();

    DB::insert("
        insert into user_account (user_id, earnings, balance) values (?, ?, ?)
        on duplicate key update
        earnings = earnings + values(earnings),
        balance = balance + values(balance)
    ", [$user->id, $order->fee * self::USER_COMMISION_RATIO, $order->fee * self::USER_COMMISION_RATIO]);

    DB::insert(sprintf("
        insert into tiger_account (`type`, order_id, user_id, profit, payment, gross_income)
            select '%s' as `type`, %d as order_id, %d as user_id, %d as profit, %d as payment, gross_income + %d as gross_income
            from tiger_account
            order by id desc
            limit 1
    ", "model", $order->id, $user->id, $order->fee, 0, $order->fee));

}, 5);

有 2 个原子查询。首先将记录向上插入到表中,另一个将记录插入到 .user_accounttiger_account

您需要事务来保证在这两个查询之间发生可怕的事情时不会应用任何更改。可怕的不是并发请求,而是php应用程序,网络分区或其他任何阻止执行第二次查询的突然死亡。在这种情况下,从回滚的第一个查询开始更改,因此数据库将保持一致状态。

这两个查询都是原子的,这保证了每个查询中的数学运算是单独完成的,并且此时没有其他查询更改表。表示 2 个并发请求可能同时处理同一用户的 2 个付款。第一个查询将在表中插入或更新记录,第二个查询将更新记录,两者都将向 添加一条记录,并且提交每个事务时,所有更改都将在数据库中永久设置。user_accounttiger_account

我做了一些假设:

  • user_id是表中的主键。user_account
  • 中至少有 1 条记录。在OP代码中调用的那个,因为当数据库中没有任何内容时,不清楚预期的行为是什么。tiger_account$old_tiger_account
  • 所有货币字段都是整数,而不是浮点数。
  • 它是MySQL DB。我使用MySQL语法来说明这种方法。其他 SQL 版本可能具有略微不同的语法。
  • 原始查询中的所有表名和列名。不记得照亮命名约定。

一句警告。这些是原始查询。将来,您应该格外小心重构模型,并编写更多的集成测试,因为一些应用程序逻辑从命令式 PHP 转变为声明式 SQL。我相信这是一个公平的价格,可以保证没有比赛条件,但我想清楚地表明它不是免费的。


答案 2

我遇到了MySQL问题的答案:事务与锁定表,它解释了事务和锁定表。它显示了此处应使用的事务和锁定。

我参考了Laravel lockforupdate(悲观锁定)如何将参数传递给Laravel DB::transaction(),然后获取下面的代码。

我不知道它是否是一个很好的实现,至少它现在有效。

DB::transaction(function ($order) use($order) {
    if($order->product_name == 'model')
    {
        $model = Model::find($order->product_id);
        $user = $model->user;

        $user_account = User_account::where('user_id', $user->id)->lockForUpdate()->first();

        if(!$user_account)
        {
            $user_account = new User_account;
            $user_account->user_id  = $user->id;
            $user_account->earnings = 0;
            $user_account->balance  = 0;
        }

        $user_account->earnings += $order->fee * self::USER_COMMISION_RATIO;
        $user_account->balance += $order->fee * self::USER_COMMISION_RATIO;
        $user_account->save();

        $old_tiger_account = Tiger_account::latest('id')->lockForUpdate()->first();
        $tiger_account = new Tiger_account;
        $tiger_account->type = 'model';
        $tiger_account->order_id = $order->id;
        $tiger_account->user_id = $user->id;
        $tiger_account->profit = $order->fee;              
        $tiger_account->payment = 0;

        if($old_tiger_account)
        {
            $tiger_account->gross_income = $old_tiger_account->gross_income + $order->fee;
        } else{
            $tiger_account->gross_income = $order->fee;
        }

        $tiger_account->save();
    }
}, 3);

推荐