it-swarm.com.de

Will laravel Datenbanktransaktionssperrtabelle?

Ich verwende die Datenbanktransaktion von laravel5.5 für die Online-Zahlungsanwendung. Ich habe eine Company_account-Tabelle, um jede Zahlung aufzuzeichnen (type, amount, create_at, gross_income). Ich muss auf den gross_income Des letzten Datensatzes zugreifen, wenn ein neuer Datensatz erstellt wird. Daher muss ich die Tabelle bei der Transaktion mit Lese- und Schreibsperre sperren, um viele Zahlungen gleichzeitig zu vermeiden.

Ich beziehe mich auf Laravels Dokument, bin mir aber nicht sicher, ob die Transaktion die Tabelle sperren wird. Wenn die Transaktion die Tabelle sperrt, welchen Sperrentyp hat sie (Lesesperre, Schreibsperre oder beides)?

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

    // create use_account record
}, 5);

Code:

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

verweise:
Übergabe eines Parameters an Laravel DB :: transaction ()

19
Kris Roofe

Da Sie 2 Tabellen aktualisieren, müssen Sie immer noch die Transaktion verwenden, um die Änderungen synchron zu halten. Betrachten Sie den folgenden Code:

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

Es gibt 2 atomare Abfragen. Zuerst wird ein Datensatz in die Tabelle user_account Eingefügt, dann wird ein Datensatz in die Tabelle tiger_account Eingefügt.

Sie benötigen die Transaktion, um sicherzustellen, dass keine Änderungen angewendet werden, wenn zwischen diesen beiden Abfragen etwas Schreckliches passiert ist. Das Schreckliche ist nicht eine gleichzeitige Anfrage, sondern ein plötzlicher Tod der PHP-Anwendung, der Netzwerkpartition oder irgendetwas anderem, das die Ausführung einer zweiten Anfrage verhindert. In diesem Fall werden Änderungen von der ersten Abfrage rückgängig gemacht, sodass die Datenbank im konsistenten Zustand bleibt.

Beide Abfragen sind atomar, was garantiert, dass die Mathematik in jeder Abfrage isoliert ausgeführt wird, und keine anderen Abfragen ändern die Tabelle zu diesem Zeitpunkt. Angenommen, es ist möglich, dass zwei gleichzeitige Anforderungen zwei Zahlungen für denselben Benutzer gleichzeitig verarbeiten. Der erste fügt einen Datensatz in die Tabelle user_account Ein oder aktualisiert sie. Die zweite Abfrage aktualisiert den Datensatz, beide fügen einen Datensatz in die Tabelle tiger_account Ein und alle Änderungen werden dauerhaft in die Datenbank übernommen wenn jede Transaktion festgeschrieben wird.

Einige Annahmen, die ich gemacht habe:

  • user_id Ist ein Primärschlüssel in der Tabelle user_account.
  • Es gibt mindestens 1 Datensatz in tiger_account. Derjenige, der im OP-Code $old_tiger_account Genannt wird, da nicht klar ist, welches Verhalten erwartet wird, wenn sich nichts in der Datenbank befindet.
  • Alle Geldfelder sind Ganzzahlen, keine Floats.
  • Es ist MySQL DB. Ich verwende die MySQL-Syntax, um den Ansatz zu veranschaulichen. Andere SQL-Varianten haben möglicherweise eine leicht abweichende Syntax.
  • Alle Tabellennamen und Spaltennamen in den unformatierten Abfragen. Erinnern Sie sich nicht, Namenskonventionen zu beleuchten.

Ein Wort der Warnung. Dies sind unformatierte Abfragen. Sie sollten beim Umgestalten von Modellen in Zukunft besondere Sorgfalt walten lassen und einige weitere Integrationstests schreiben, da einige Anwendungslogiken von imperativem PHP zu deklarativem SQL übergegangen sind. Ich glaube, es ist ein fairer Preis, keine Rennbedingungen zu garantieren, aber ich möchte klarstellen, dass es nicht kostenlos ist.

9
Alex Blex

Ich bin auf diese Antwort der Frage MySQL: Transactions vs Locking Tables gestoßen, die die Transaktion und die Sperrtabelle erklären. Es werden sowohl die Transaktion als auch die Sperrung angezeigt, die hier verwendet werden sollen.

Ich beziehe mich auf Laravel lockforupdate (Pessimistic Locking) und Übergabe des Parameters an Laravel DB :: transaction () , und erhalte dann den folgenden Code .

Ich weiß nicht, ob es eine gute Implementierung ist , zumindest funktioniert es jetzt.

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

Wenn Sie meiner Meinung nach das Bruttoeinkommen für jeden Datensatz direkt separat berechnen, müssen Sie die Tabelle nicht einmal sperren. Sie wissen, dass das Sperren einer Tabelle Ihre Website direkt verlangsamt.

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