Laravelx和MySQL如何对数百万条记录发出更新



我有一个项目,其中表中的某些记录将"过期";过了这么多天。我使用软删除,所以基本上我要做的就是设置deleted_at列。目前生成的SQL如下:

update st_photos set deleted_at = '2022-05-03 05:00:01', updated_at = '2022-05-03 05:00:01' 
where user_id in (select id from users where account_code = 'S') and created_at < '2022-03-04 05:00:01' and in_storage = 0 and deleted_at is null

现在只有大约1600万条记录,更新语句大约需要65秒才能完成。发生的情况是,当上面的更新第一次开始时,如果其他代码试图将一条记录插入到该表中,则插入失败并产生锁等待超时。

是否有一种方法,以某种方式打破更新语句成更小的批给其他sql命令时间运行?我最关心的不是更新过程需要多长时间,我只是不希望任何给定的批处理更新运行超过30秒,这样它就不会锁定其他sql命令太长时间。

我可以做到的一种方法是将用户分组在子选择中,以便它批量处理照片,但我想知道是否有人有更好,更容易实现的方法。

如果有帮助的话,下面是生成上述SQL的初级代码片段。

self::getExpiredMediaQuery(AppConstants::ACCOUNT_CODE_STANDARD)->update(['deleted_at' => $now, 'updated_at' => $now]);
...
public static function getExpiredMediaQuery($accountCode) {
$dateExpired = Carbon::now()->subDays(AppConstants::$accountProperties[$accountCode]['mediaRetentionDays']);
$query = Photo::whereIn('user_id', function ($query) use ($accountCode) {
$query->select('id')->from('users')
->where('account_code', $accountCode);
})
->where('created_at', '<', $dateExpired)
->where('in_storage', false);
return $query;
}

使用一些分块方法,比如chunkById()。像这样的东西应该可以把工作分解成可管理的部分。

self::getExpiredMediaQuery(AppConstants::ACCOUNT_CODE_STANDARD)
->chunkById(
1000,
fn(Photo $p) => $p->update(['deleted_at' => $now, 'updated_at' => $now])
);

我有兴趣看到评论中建议的最新参考,WHERE … IN很慢,但正如前面提到的,方法中的查询可以通过连接生成,尽管使用查询生成器比将原始查询转储到DB的调用中更好:

Photo::join("users", "photos.user_id", "=", "users.id")
->where("photos.in_storage", false)
->where("photos.created_at", "<", $date_expired)
->where("users.account_code", $accountCode)
// the joined values are not Eloquent models
// if there are soft deletes they have to be filtered manually
->whereNull("users.deleted_at");

然后可以使用与上面相同的分块方法。

你可以使用Laravel Job批处理这类任务,如果你有更多的记录,这将是更容易处理的方法。更复杂的操作

查看这个laravel文档获取更多

最新更新