如何在现有 Eloquent 模型上 `lockForUpdate()`?

How to `lockForUpdate()` on an existing Eloquent model?

lockForUpdate()sharedLock() 是 Laravel Eloquent 中的函数,用于设置独占锁或共享锁(文档 here)。

但是,我找不到一个很好的语法来将其应用到一个已经实例化的 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() returns 这里有一个新的查询生成器,它将使所有用户的余额增加一。

我希望 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 .

嗯,看来我设法快速解决了这个问题。

我认为预期的方法是这样做:

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