我有很多要从数据库中删除的项目(1M +),我派出了一个后台作业来解决这个问题,因此用户不必等待它完成就可以继续进行无论他/她在做什么,问题是,在删除项目时,应用程序变得无响应,所以我认为我将逐块处理项目并睡眠几秒钟,然后继续。
以下是处理删除的代码:
// laravel job class
// ...
public function handle()
{
$posts_archive = PostArchive::find(1); // just for the purpose of testing ;)
Post::where('arch_id', $posts_archive->id)->chunk(1000, function ($posts) {
//go through the collection and delete every post.
foreach($posts as $post) {
$post->delete();
}
// throttle
sleep(2);
});
}
预期结果:将帖子分块并处理每个块,然后闲置2秒钟,重复此操作,直到所有项目都被删除。
实际结果:随机数量的项目被删除一次,然后该过程结束。没有错误,没有指标,没有线索?
有没有更好的方法来实现这一点?
答案 0 :(得分:4)
Laravel对此的处理方式没有任何具体说明。听起来如果作业中的删除查询冻结了UI的其余部分,则您的数据库服务器需要检查或优化。
检索每个模型并单独运行删除查询绝对不是优化此方法的好方法,因为您将要执行数百万个查询。如果希望尝试限制应用程序中的每秒负载,而不是优化数据库服务器来处理此查询,则可以使用带有删除限制的while循环:
do {
$deleted = Post::where('arch_id', $posts_archive->id)->limit(1000)->delete();
sleep(2);
} while ($deleted > 0);
答案 1 :(得分:3)
您的实际结果与预期结果不同的原因在于Laravel如何对数据集进行分块。
Laravel一次遍历您的数据集一页(),并将Post
模型的集合传递给您的回调。
由于要删除集合中的记录,因此Laravel在每次迭代中都会有效地跳过一页数据,因此最终会丢失大约原始查询中一半的数据。
采取以下情况–您希望在 10个大块中删除 24个记录:
期望
+-------------+--------------------+---------------------------+ | Iteration | Eloquent query | Rows returned to callback | +-------------+--------------------+---------------------------+ | Iteration 1 | OFFSET 0 LIMIT 10 | 10 | | Iteration 2 | OFFSET 10 LIMIT 10 | 10 | | Iteration 3 | OFFSET 20 LIMIT 10 | 4 | +-------------+--------------------+---------------------------+
实际
+-------------+--------------------+----------------------------+ | Iteration | Eloquent query | Rows returned to callback | +-------------+--------------------+----------------------------+ | Iteration 1 | OFFSET 0 LIMIT 10 | 10 | (« but these are deleted) | Iteration 2 | OFFSET 10 LIMIT 10 | 4 | | Iteration 3 | NONE | NONE | +-------------+--------------------+----------------------------+
第一次迭代后,只剩下14条记录,因此当Laravel获取第2页时,它仅找到4条记录。
结果是,删除了24条记录中的14条记录,这听起来有些随机,但是就Laravel如何处理数据而言才有意义。
该问题的另一种解决方案是use a cursor处理查询,这将一次遍历数据库结果集1记录,这更好地利用了内存。
例如
// laravel job class
// ...
public function handle()
{
$posts_archive = PostArchive::find(1); // just for the purpose of testing ;)
$query = Post::where('arch_id', $posts_archive->id);
foreach ($query->cursor() as $post) {
$post->delete();
}
}
注意:如果您只想删除数据库中的记录,则此处的其他解决方案更好。如果需要进行其他处理,那么使用游标会是一个更好的选择。
答案 2 :(得分:1)
正如开尔文·琼斯(Kelvin Jones)所指出的,删除随机项的原因是您在翻阅记录时正在删除记录。
Array
(
[Andew] => [0,480,600]
[Jon] => [120,110,0]
[Walid] => [0,0,160]
)
仅使用offset&limit在表中“分页”。但是,如果您从第1页上删除100条记录(ID为1-100),然后转到第2页,则实际上是在跳过ID 101-200,而跳到201-300。
chunk
是解决这个问题的方法
chunkById
从字面上看,只需替换方法名称即可。现在,将使用第一页的最大主键(100),而不是使用offset&limit进行分页,然后下一页将查询Post::where('arch_id', $posts_archive->id)->chunkById(1000, function ($posts) {
//go through the collection and delete every post.
foreach($posts as $post) {
$post->delete();
}
});
。因此,第2页现在正确地为您提供了ID 101-200,而不是201-300。
答案 3 :(得分:0)
如果我理解正确,问题是删除大量条目会占用太多资源。 一次写一篇文章会花费太长时间。
尝试获取post.id的最小值和最大值,然后对类似
的那些进行分块for($i = $minId; $i <= $maxId-1000; $i+1000) {
Post::where('arch_id', $posts_archive->id)->whereBetween('id', [$i, $i+1000])->delete();
sleep(2);
}
自定义块和睡眠时间,以适合您的服务器资源。