php - Laravel Eloquent 悲观锁更新余额
问题描述
我有一个包含余额的表,当用户购买商品时会扣除余额,我试图锁定以确保余额有效并正确扣除。我有一个 for 循环,它需要多个余额并在请求中一一减少它们。我所做的是用 DB:transaction 包装我的代码,如下所示,但这似乎是错误的,因为 for 循环可能需要比预期更长的时间,因此每个其他用户都试图更新这个余额(!!这是为许多用户共享的可以同时编辑!!)
$balances->map(function ($balance) {
// Check if balance is valid
if (!Balances::where('id', $balance->id)->deductBalance($balance->balance)) {
return response()->json(['message' => 'Insufficient '], 400);
};
DB:create([
....
]);
});
扣除余额:
public function deductBalance($balance) {
$this->balance-= $balance;
if ($this->balance >= 0) {
$this->save();
return true;
}
return false;
}
我添加了这个,如文档中所示
DB::transaction(function () {
// Check if balance is valid
if (!Balances::where('id', $balance->id)->deductBalance($balance->balance)) {
return response()->json(['message' => 'Insufficient '], 400);
};
}, 5);
想象一下 5 个用户同时尝试更新 5 个余额,上述解决方案是否有效地防止余额为负值?我已经可以看到这个for循环的一个问题,如果一个余额无效,请求将被拒绝,但所有以前的余额都会被扣除,我是否应该在事务中再有一个for循环先检查所有余额然后更新它们?谢谢。
解决方案
DB::transaction()
不锁定任何东西。如果其中一个失败,它只是确保回滚事务中的每个查询。
如果要锁定资源,您应该执行以下操作:
DB::transaction(function () use ($reserva, $validated) {
// lock
$balance = Balances::lockForUpdate()->find($balance->id) // or where->(...)->get() if it's more than 1 balance.
...
});
lockForUpdate()
在事务内部防止任何查询影响选定的资源,直到事务结束。
在这种情况下,对 id = 1 有影响的更新/删除查询Balance
将被搁置。其他余额 (id != 1) 可以更新且未锁定。
编辑
这是查看数据库锁的一种简单方法。
- 在一个终端上运行
php artisan tinker
并运行以下代码
不要关闭终端。DB::transaction(function () { SomeTestModel::lockForUpdate()->find(1)->update(['attribute' => 'value']); sleep(60); // artificially increase how long the transaction is going to take by 60 seconds. });
- 在另一个终端上,运行
php artisan tinker
并运行以下代码:// Will run instantly SomeTestModel::find(2)->update(['attribute' => 'value']); // Will not run until transaction on previous terminal is done. Might even throw a lock wait timeout, showing the resource is properly locked. SomeTestModel::find(1)->update(['attribute' => 'value']);
推荐阅读
- powershell - Powershell脚本运行但任务调度程序不会运行它
- python - Pandas 遍历数据框中单列的值
- excel - 从列中拆分单词并根据数组中的条件重新加入
- sql - 函数 PL/SQL ORACLE
- android - 如何从 Kotlin 的 recyclerview 获取 Checkbox isChecked 数据
- java - JAVA 正则表达式识别值并对路径参数进行分类
- angular - Angular 11(或 9+)I18n 在启动时设置语言环境
- java - 具有非 void 返回值的方法的 Java Spring 异步异常处理
- activemq - 如何通过命令行获取 Apache ActiveMQ 的统计信息
- node.js - Connect-mongodb-session: TypeError: Cannot read property 'Store' of undefined (mongodb, nodejs, connect-mongodb-session)