雄辩的块()缺少一半的结果

2022-08-30 16:18:53

我对Laravel的ORM Eloquent chunk()方法有问题。它错过了一些结果。下面是一个测试查询:

$destinataires = Destinataire::where('statut', '<', 3)
    ->where('tokenized_at', '<', $date_active)
    ->chunk($this->chunk, function ($destinataires) {
        foreach($destinataires as $destinataire) {
            $this->i++;
        }
    }
echo $this->i;

它给出了124838的结果。

但:

$num_dest = Destinataire::where('statut', '<', 3)
    ->where('tokenized_at', '<', $date_active)
    ->count();
echo $num_dest;

给出了249676,所以只有TWICE作为第一个代码示例。

我的脚本应该编辑数据库中所有匹配的记录。如果我多次启动它,它每次只分发剩余记录的一半。

我尝试使用DB::table()而不是模型。我试图添加一个->take(20000),但它似乎没有被考虑在内。我用->toSql()回显了查询,并且所有内容似乎都很好(当我添加->take()参数时添加了LIMIT子句)。

有什么建议吗?


答案 1

假设您正在使用块方法来删除所有记录。该表有 2,000,000 条记录,您将按 1000 个块删除所有这些记录。

$query->orderBy('id')->chunk(1000, function ($items) {
    foreach($items as $item) {
        $item->delete();
    }
});

它将通过在查询中获取前 1000 条记录来删除前 1000 条记录,如下所示:

SELECT * FROM table ORDER BY id LIMIT 0,1000

然后,来自 chunk 方法的另一个查询是:

SELECT * FROM table ORDER BY id LIMIT 1000,2000

我们的问题就在这里,我们删除了 1000 条记录,然后得到从 1000 条到 2000 条记录的结果。实际上,我们缺少前 1000 条记录,这意味着我们不会在块的第一步中删除 1000 条记录!对于其他步骤,此方案将相同。在每一步中,我们都会错过1000条记录,这就是我们在这些情况下没有获得最佳结果的原因。

我做了一个删除的例子,因为这样我们就可以知道块方法的确切行为。


更新:

您可以使用 安全删除。chunkById()

在此处阅读更多内容:

http://laravel.at.jeffsbox.eu/laravel-5-eloquent-builder-chunk-chunkbyid https://laravel.com/api/5.4/Illuminate/Database/Eloquent/Builder.html#method_chunkById


答案 2

快速回答:请代替 使用 。chunkById()chunk()

在循环访问记录时更新或删除记录时,对主键或外键的任何更改都可能会影响块查询。这可能会导致记录未包含在结果中。

解释可以在Laravel文档中找到:

下面是解决方案示例:

DB::table('users')->where('active', false)
    ->chunkById(100, function ($users) {
        foreach ($users as $user) {
            DB::table('users')
                ->where('id', $user->id)
                ->update(['active' => true]);
        }
    });

如果在对结果进行分块时更新数据库记录,则分块结果可能会以意外的方式更改。如果计划在分块时更新检索到的记录,则最好改用 chunkById 方法。此方法将根据记录的主键自动对结果进行分页。

(更新结束)

原来的答案:

我遇到了同样的问题 - 只有一半的总结果被传递给chunk()方法的回调函数。

下面是有问题的代码:

Transaction::whereNull('processed')->chunk(100, function ($transactions) {
    $transactions->each(function($transaction){
        $transaction->process();
    });
});

我使用了Laravel 5.4,并设法解决了用cursor()方法替换chunk()方法并相应地更改代码的问题:

foreach (Transaction::whereNull('processed')->cursor() as $transaction) {
    $transaction->process();
}

即使答案不能解决问题本身,它也提供了一个有价值的解决方案。


推荐