Laravel 块和删除

xan*_*dev 2 php jobs laravel eloquent

我有大量要从数据库中删除的项目(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);
    });
}
Run Code Online (Sandbox Code Playgroud)

预期结果:帖子被分块并处理每个块,然后空闲 2 秒,重复此操作直到删除所有项目。

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

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

Dev*_*von 9

Laravel 没有任何关于你处理这个问题的具体方式。如果作业中的删除查询冻结了 UI 的其余部分,听起来您的数据库服务器需要审查或优化。

检索每个模型并单独运行删除查询绝对不是优化此操作的好方法,因为您将执行数百万个查询。如果您希望尝试限制应用程序中的每秒负载而不是优化数据库服务器来处理此查询,则可以使用带有删除限制的 while 循环:

do {
    $deleted = Post::where('arch_id', $posts_archive->id)->limit(1000)->delete();
    sleep(2);
} while ($deleted > 0);
Run Code Online (Sandbox Code Playgroud)


Kel*_*nes 9

您的实际结果与预期结果不同的原因在于 Laravel 如何分块您的数据集。

Laravel 一次对你的数据集进行一页分页,并将Post模型集合传递给你的回调。

由于您要删除集合中的记录,Laravel 会在每次迭代时有效地跳过一页数据,因此您最终会丢失原始查询中大约一半的数据。

考虑以下场景 -您希望以10 块为单位删除24 条记录

预期的

+------------+------------+-------------- -------------+
| 迭代 | 雄辩的查询 | 行返回回调 |
+------------+------------+-------------- -------------+
| 迭代 1 | 偏移 0 限制 10 | 10 |
| 迭代 2 | 偏移 10 限制 10 | 10 |
| 迭代 3 | 偏移 20 限制 10 | 4 |
+------------+------------+-------------- -------------+

实际的

+------------+------------+-------------- --------------+
| 迭代 | 雄辩的查询 | 行返回回调 |
+------------+------------+-------------- --------------+
| 迭代 1 | 偏移 0 限制 10 | 10 | (« 但这些都被删除了)
| 迭代 2 | 偏移 10 限制 10 | 4 |
| 迭代 3 | 无 | 无 |
+------------+------------+-------------- --------------+

第一次迭代后,只剩下 14 条记录,所以当 Laravel 获取第 2 页时,它只找到了 4 条记录。

结果是,24 条记录中有 14 条被删除,这感觉有点随机,但就 Laravel 处理数据的方式而言是有道理的。

该问题的另一种解决方案是使用游标来处理您的查询,这将一次遍历您的数据库结果集 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:如果您只想删除数据库中的记录,这里的其他解决方案会更好。如果您需要进行任何其他处理,那么使用游标将是更好的选择。

  • @BlueC 是的,这正是 `chunkById` 的用途 (2认同)