web-dev-qa-db-ja.com

laravelデータベーストランザクションロックテーブル?

オンライン支払いアプリケーションにlaravel5.5のデータベーストランザクションを使用しています。各支払いを記録するcompany_accountテーブルがあります(typeamountcreate_atgross_income)。新しいレコードが作成されたときに、最後のレコードのgross_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() に渡す方法

19
Kris Roofe

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_accountテーブルにアップサートし、別のレコードはtiger_accountにレコードを挿入します。

これら2つのクエリの間にひどいことが起こった場合、変更が適用されないことを保証するトランザクションが必要です。恐ろしいことは、同時リクエストではなく、PHPアプリケーション、ネットワークパーティション、または2番目のクエリの実行を妨げるその他のものの突然の死です。この場合、最初のクエリのロールバックからの変更により、データベースは一貫した状態のままになります。

両方のクエリはアトミックであり、各クエリの計算が単独で行われることを保証し、現時点では他のクエリはテーブルを変更しません。 2つの同時要求が同じユーザーの2つの支払いを同時に処理する可能性があると言います。最初のクエリはuser_accountテーブルのレコードを挿入または更新し、2番目のクエリはレコードを更新します。両方ともtiger_accountにレコードを追加し、それぞれがトランザクションがコミットされます。

私がしたいくつかの仮定:

  • user_idは、user_accountテーブルの主キーです。
  • tiger_accountには少なくとも1つのレコードがあります。 OPに$old_tiger_accountと呼ばれるものがあります。これは、dbに何もない場合に予想される動作が明確でないためです。
  • すべてのmoneyフィールドは、floatではなく整数です。
  • MySQL DBです。 MySQL構文を使用してアプローチを説明します。他のSQLフレーバーは、わずかに異なる構文を持つ場合があります。
  • 生のクエリのすべてのテーブル名と列名。命名規則を明確にすることを忘れないでください。

警告の言葉。これらは生のクエリです。将来的にはリファクタリングモデルに細心の注意を払い、いくつかのアプリケーションロジックを命令型PHPから宣言型SQLに移行しました。レース条件、それでも私はそれが無料ではないことを明確にしたい。

9
Alex Blex

私はこれに遭遇しました answer 質問 MySQL:Transactions vs Locking Tables 、これはトランザクションとロックテーブルを説明しています。ここでは、トランザクションとロックの両方を使用する必要があることを示しています。

Laravel lockforupdate(Pessimistic Locking) および パラメーターを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);
5
Kris Roofe

私の意見では、各レコードのオンザフライで総収入を個別に計算すると、テーブルをロックする必要さえなく、テーブルをロックするとウェブサイトが直接遅くなることがわかっています。

DB::transaction(function () use($order) {
    $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
    $tiger_account = Tiger_account::create([
        'type' => 'model',
        'order_id' => $order->id,
        'user_id' => $user->id,
        'profit' => $order->fee,
        'payment' => 0,
    ]);

    $tiger_account->update([
        'gross_income' => Tiger_account::where('id', '<=', $tiger_account->id)->sum('fee'),
    ]);
});
1
spirit