如何在已有的Eloquent模型上使用`lockForUpdate()`方法进行锁定?

5

lockForUpdate()sharedLock() 是 Laravel Eloquent 中用于设置排他或共享锁的函数(文档在这里)。

然而,我找不到一个好的语法来对单个已实例化的 Eloquent 模型应用此功能。考虑以下示例代码:

DB::transaction(function() {
    // Find the user with ID = 1.
    $user = User::find(1);
    $user->lockForUpdate()->update([
        'balance' => $user->balance + 1
    ]);

    // ... some more stuff happens here in the transaction
});

上述代码将不会按预期工作。在此处,lockForUpdate() 返回一个新的查询构建器,这将导致所有用户的余额增加1。
我想在此事务的持续时间内读取锁定balance属性,以便发生并行处理的任何其他事务都不会通过计算错误的结果而使账户余额出现偏差。那么,如何确保在更新此用户时锁定balance属性?我知道可以调用以下函数,但为此创建一个新查询似乎有点违反直觉,其中还包括$user变量:
$updated = User::query()->where('id', 1)->lockForUpdate()->update([
    'balance' => $user->balance
]);

注意:我希望在这里不考虑->increment()->decrement()。我无法使用这些函数,因为我需要Eloquent的updating/updated/saving/saved事件挂钩正常触发(如果使用这些函数则无法触发)。这是可以预料的,如需更多信息,请参见https://github.com/laravel/framework/issues/18802#issuecomment-593031090
1个回答

7

哦,看来我成功地找到了这个问题的快速解决方法。

我认为正确的方法是这样做:

DB::transaction(function() {
    // You can also use `findOrFail(1)` or any other query builder functions here depending on your needs.
    $user = User::lockForUpdate()->find(1);
    $user->update([
        'balance' => $user->balance + 1
    ]);
});

这将生成以下SQL语句(从MySQL通用查询日志摘录):

200524 13:36:04    178 Query    START TRANSACTION
178 Prepare select * from `users` where `users`.`id` = ? limit 1 for update
178 Execute select * from `users` where `users`.`id` = 1 limit 1 for update
178 Close stmt  
178 Prepare update `users` set `balance` = ?, `users`.`updated_at` = ? where `id` = ?
178 Execute update `users` set `balance` = 15, `users`.`updated_at` = '2020-05-24 13:36:04' where `id` = 1
178 Close stmt
QUERY     COMMIT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接