Laravel块并删除

时间:2018-09-24 16:09:49

标签: php laravel eloquent jobs

我有很多要从数据库中删除的项目(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秒钟,重复此操作,直到所有项目都被删除。

实际结果:随机数量的项目被删除一次,然后该过程结束。没有错误,没有指标,没有线索?

有没有更好的方法来实现这一点?

4 个答案:

答案 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);
}

自定义块和睡眠时间,以适合您的服务器资源。