Laravel `increment()` 是否锁定了行?
Does the Laravel `increment()` lock the row?
在 Eloquent 模型上调用 Laravel increment()
是否锁定行?
例如:
$userPoints = UsersPoints::where('user_id','=',\Auth::id())->first();
if(isset($userPoints)) {
$userPoints->increment('points', 5);
}
如果在竞争条件下从两个不同的位置调用它,第二个调用是否会覆盖第一个增量并且我们仍然只得到 5 分?或者他们会加起来,我们最终得到 10 分吗?
回答这个问题(对以后的读者有帮助):你问的问题取决于数据库配置。
大多数 MySQL 引擎:MyISAM 和 InnoDB 等。在插入、更新或更改 table 时使用锁定,直到明确关闭此功能。 (无论如何,在大多数情况下,这是唯一正确且可以理解的实现方式)
因此您可以对所得到的感到满意table,因为它可以在任意数量的并发调用中正常工作:
-- this is something like what laravel query builder translates to
UPDATE users SET points += 5 WHERE user_id = 1
并以零为起始值调用两次,最终将达到 10
对于 Laravel 中 ->increment()
的具体情况,答案实际上略有不同:
如果调用 $user->increment('credits', 1)
,将执行以下查询:
UPDATE `users`
SET `credits` = `credits` + 1
WHERE `id` = 2
这意味着查询可以被视为 atomic,因为实际的 credits
金额是在查询中检索的,而不是使用单独的 [=17] 检索的=].
因此您可以在没有 运行 任何 DB::transaction()
包装器或 lockForUpdate()
调用的情况下执行此查询,因为它总是会正确地增加它。
为了显示可能出错的地方,BAD 查询将如下所示:
# Assume this retrieves "5" as the amount of credits:
SELECT `credits` FROM `users` WHERE `id` = 2;
# Now, execute the UPDATE statement separately:
UPDATE `users`
SET `credits` = 5 + 1, `users`.`updated_at` = '2022-04-15 23:54:52'
WHERE `id` = 2;
或 Laravel 等价物(不要这样做):
$user = User::find(2);
// $user->credits will be 5.
$user->update([
// Shown as "5 + 1" in the query above but it would be just "6" ofcourse.
'credits' => $user->credits + 1
]);
现在,这很容易出错,因为您是 'assigning' 信用值,这取决于 SELECT 语句发生的时间。因此 2 个查询可以将信用更新为相同的值,而意图是将其递增两次。但是,您可以通过以下方式更正此 Laravel 代码:
DB::transaction(function() {
$user = User::query()->lockForUpdate()->find(2);
$user->update([
'credits' => $user->credits + 1,
]);
});
现在,由于 2 个查询包含在一个事务中并且 ID 为 2 的用户记录是 READ-locked 使用 lockForUpdate()
,任何第二个(或第三个或 n-th)实例在锁定事务完成之前,不应使用 SELECT 查询读取此并行发生的事务。
在 Eloquent 模型上调用 Laravel increment()
是否锁定行?
例如:
$userPoints = UsersPoints::where('user_id','=',\Auth::id())->first();
if(isset($userPoints)) {
$userPoints->increment('points', 5);
}
如果在竞争条件下从两个不同的位置调用它,第二个调用是否会覆盖第一个增量并且我们仍然只得到 5 分?或者他们会加起来,我们最终得到 10 分吗?
回答这个问题(对以后的读者有帮助):你问的问题取决于数据库配置。
大多数 MySQL 引擎:MyISAM 和 InnoDB 等。在插入、更新或更改 table 时使用锁定,直到明确关闭此功能。 (无论如何,在大多数情况下,这是唯一正确且可以理解的实现方式)
因此您可以对所得到的感到满意table,因为它可以在任意数量的并发调用中正常工作:
-- this is something like what laravel query builder translates to
UPDATE users SET points += 5 WHERE user_id = 1
并以零为起始值调用两次,最终将达到 10
对于 Laravel 中 ->increment()
的具体情况,答案实际上略有不同:
如果调用 $user->increment('credits', 1)
,将执行以下查询:
UPDATE `users`
SET `credits` = `credits` + 1
WHERE `id` = 2
这意味着查询可以被视为 atomic,因为实际的 credits
金额是在查询中检索的,而不是使用单独的 [=17] 检索的=].
因此您可以在没有 运行 任何 DB::transaction()
包装器或 lockForUpdate()
调用的情况下执行此查询,因为它总是会正确地增加它。
为了显示可能出错的地方,BAD 查询将如下所示:
# Assume this retrieves "5" as the amount of credits:
SELECT `credits` FROM `users` WHERE `id` = 2;
# Now, execute the UPDATE statement separately:
UPDATE `users`
SET `credits` = 5 + 1, `users`.`updated_at` = '2022-04-15 23:54:52'
WHERE `id` = 2;
或 Laravel 等价物(不要这样做):
$user = User::find(2);
// $user->credits will be 5.
$user->update([
// Shown as "5 + 1" in the query above but it would be just "6" ofcourse.
'credits' => $user->credits + 1
]);
现在,这很容易出错,因为您是 'assigning' 信用值,这取决于 SELECT 语句发生的时间。因此 2 个查询可以将信用更新为相同的值,而意图是将其递增两次。但是,您可以通过以下方式更正此 Laravel 代码:
DB::transaction(function() {
$user = User::query()->lockForUpdate()->find(2);
$user->update([
'credits' => $user->credits + 1,
]);
});
现在,由于 2 个查询包含在一个事务中并且 ID 为 2 的用户记录是 READ-locked 使用 lockForUpdate()
,任何第二个(或第三个或 n-th)实例在锁定事务完成之前,不应使用 SELECT 查询读取此并行发生的事务。