Подтвердить что ты не робот

Будет ли таблица блокировки транзакций базы данных laravel?

Я использую транзакцию базы данных laravel5.5 для приложения онлайн-платежей. У меня есть таблица company_account для записи каждого платежа (type, amount, create_at, gross_income). Мне нужно получить доступ к последней записи gross_income, когда создается новая запись. Поэтому мне нужно заблокировать таблицу при транзакции с блокировкой таблицы чтения и записи, чтобы избежать многих платежей одновременно.

Я ссылаюсь на laravel doc, но я не уверен, что транзакция заблокирует таблицу. Если транзакция заблокирует таблицу, какой тип блокировки (блокировка чтения, блокировка записи или обе)?

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 ::action()

4b9b3361

Ответ 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_account, другую вставьте запись в tiger_account.

Вам нужна транзакция, чтобы гарантировать, что никаких изменений не будет применено, если между этими двумя запросами произойдет что-то ужасное. Страшная вещь - не параллельный запрос, а внезапная смерть приложения php, сетевого раздела или чего-то еще, что предотвращает выполнение второго запроса. В этом случае изменения от первого запроса откатываются, поэтому база данных остается в согласованном состоянии.

Оба запроса являются атомарными, что гарантирует, что математика в каждом запросе выполняется изолированно, и никакие другие запросы не изменяют таблицу в это время. Сказать, что возможно, что 2 одновременных запроса обрабатывают 2 платежа для одного и того же пользователя одновременно. Первый будет вставлять или обновлять запись в таблице user_account, а второй запрос будет обновлять запись, оба добавят запись в tiger_account, и все изменения будут постоянно установлены в db, когда каждая транзакция будет зафиксирована.

Несколько допущений, которые я сделал:

  • user_id является первичным ключом в таблице user_account.
  • В tiger_account есть как минимум 1 запись. Тот, который называется $old_tiger_account в OP-коде, поскольку неясно, какое ожидаемое поведение, когда в db ничего нет.
  • Все денежные поля являются целыми числами, а не плавают.
  • Это 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);

Ответ 3

На мой взгляд, если вы вычисляете валовой доход "на лету" для каждой записи, отдельно, вам даже не нужно блокировать таблицу, вы знаете, что блокировка таблицы напрямую замедлит ваш сайт.

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'),
    ]);
});