php - example - ¿Laravel base de datos de la tabla de bloqueo de transacciones?
transacciones laravel (3)
En mi opinión, si calcula los ingresos brutos sobre la marcha para cada registro, por separado, ni siquiera necesita bloquear la tabla, sabe que bloquear una tabla ralentizará directamente su sitio web.
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''),
]);
});
Utilizo la transacción de la base de datos de laravel5.5 para la aplicación de pago en línea. Tengo una tabla company_account para registrar cada pago ( type
, amount
, create_at
, gross_income
). Necesito acceder al gross_income
del último registro, cuando se crea un nuevo registro. Así que necesito bloquear la tabla cuando la transacción con la tabla de lectura y escritura se bloquea para evitar muchos pagos al mismo tiempo.
Me he referido al documento de laravel, pero no estoy seguro de si la transacción bloqueará la tabla. Si la transacción bloqueará la tabla, ¿cuál es el tipo de bloqueo (bloqueo de lectura, bloqueo de escritura o ambos)?
DB::transaction(function () {
// create company_account record
// create use_account record
}, 5);
Código:
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);
referencias:
Cómo pasar el parámetro a Laravel DB :: transaction ()
Me encontré con esta answer de la pregunta MySQL: Transacciones vs Tablas de bloqueo , que explican la transacción y la tabla de bloqueo. Muestra que tanto la transacción como el bloqueo deben usarse aquí.
Me refiero a Laravel lockforupdate (Bloqueo pesimista) y Cómo pasar el parámetro a Laravel DB :: transaction () , luego obtengo el código debajo.
No sé si es una buena implementación , al menos ahora funciona.
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);
Ya que está actualizando 2 tablas, aún necesita usar la transacción para mantener los cambios sincronizados. Considere el siguiente código:
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);
Hay 2 consultas atómicas. Primero, uno sube un registro a la tabla user_account
, otro inserta un registro en tiger_account
.
Necesita la transacción para garantizar que no se apliquen cambios si sucedió algo terrible entre estas 2 consultas. Lo terrible no es una solicitud concurrente, sino una muerte repentina de la aplicación php, la partición de red o cualquier otra cosa que impida la ejecución de una segunda consulta. En este caso, los cambios a partir de la primera consulta revertida, por lo que la base de datos permanece en un estado consistente.
Ambas consultas son atómicas, lo que garantiza que las matemáticas en cada consulta se realicen de forma aislada, y ninguna otra consulta cambia la tabla en este momento. Diciendo que es posible que 2 solicitudes simultáneas procesen 2 pagos para el mismo usuario al mismo tiempo. La primera insertará o actualizará un registro en la tabla user_account
y la segunda consulta actualizará el registro, ambas agregarán un registro a tiger_account
, y todos los cambios se establecerán permanentemente en la base de datos cuando se confirme cada transacción.
Pocos supuestos que hice:
-
user_id
es una clave principal en la tablauser_account
. - Hay al menos 1 registro en
tiger_account
. El llamado$old_tiger_account
en el código OP, ya que no está claro cuál es el comportamiento esperado cuando no hay nada en la base de datos. - Todos los campos de dinero son enteros, no flotantes.
- Es MySQL DB. Utilizo la sintaxis de MySQL para ilustrar el enfoque. Otros tipos de SQL pueden tener una sintaxis ligeramente diferente.
- Todos los nombres de tablas y columnas en las consultas en bruto. No recuerdo iluminar convenciones de nomenclatura.
Una advertencia . Estas son las consultas en bruto. Debería tener un cuidado especial en los modelos de refactorización en el futuro y escribir algunas pruebas de integración más, ya que algunas aplicaciones lógicas cambiaron de PHP imperativo a SQL declarativo. Creo que es un precio justo para garantizar que no haya condiciones de carrera, pero quiero que quede claro que no es gratis.