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

Я использую транзакцию базы данных laravel5.5 для приложения онлайн-платежей. У меня есть таблица company_account для записи каждого платежа (type, amount, create_at, gross_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 ::action ()

16

Решение

Поскольку вы обновляете 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и все изменения будут постоянно установлены в БД, когда каждая транзакция будет зафиксирована.

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

  • user_id является первичным ключом в user_account Таблица.
  • Существует как минимум 1 запись в tiger_account, Тот, который называется $old_tiger_account в коде OP, так как не ясно, каково ожидаемое поведение, когда в БД ничего нет.
  • Все денежные поля являются целыми числами, а не числами с плавающей точкой.
  • Это БД MySQL. Я использую синтаксис MySQL для иллюстрации подхода. Другие разновидности SQL могут иметь немного другой синтаксис.
  • Все имена таблиц и столбцов в необработанных запросах. Не забывайте освещать соглашения об именах.

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

8

Другие решения

Я сталкивался с этим ответ вопроса MySQL: транзакции и таблицы блокировки, которые объясняют транзакцию и блокировку таблицы. Это показывает, что транзакция и блокировка должны использоваться здесь.

Я имею в виду Laravel lockforupdate (Пессимистическая блокировка) а также Как передать параметр в Laravel DB ::action (), тогда получите код ниже.

Я не знаю, хорошая ли это реализация, по крайней мере, сейчас это работает.

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

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

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
По вопросам рекламы [email protected]