【问题标题】:Eloquent chunk() missing half the results雄辩的块()缺少一半的结果
【发布时间】:2015-12-18 11:12:27
【问题描述】:

我对 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 子句)。

有什么建议吗?

【问题讨论】:

  • $this->chunk 输出什么?
  • 它是一个整数(即2000),我将它重命名为$chunk_size,它没有帮助。
  • 你能看到那个变量的输出或值是什么吗?
  • 当然:int(2000)(即使经过几次迭代)
  • @adelinemr 我发现了我的情况的原因,但您的情况可能存在不同的问题。我正在更新循环内的模型,原始查询条件在每次迭代时返回不同的结果。

标签: php laravel laravel-5 eloquent


【解决方案1】:

对于任何正在寻找解决此问题的代码的人,请看:

while (Model::where('x', '>', 'y')->count() > 0)
{
    Model::where('x', '>', 'y')->chunk(10, function ($models)
    {
        foreach ($models as $model)
        {
            $model->delete();
        }
    });
}

问题在于删除/删除模型,同时将整体分块。将它包含在一个 while 循环中可以确保你得到它们!此示例在删除模型时有效,更改 while 条件以满足您的需要!

【讨论】:

  • 在我意识到 chunk() 中的 delete() 正在弄乱它的分页之前,我花了一个小时挠头,所以你的解决方案为我节省了时间。谢谢。
【解决方案2】:

快速回答:使用chunkById() 而不是chunk()

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

解释可以在Laravel documentation:

以下是解决方案示例:

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();
}

尽管答案本身并不能解决问题,但它提供了有价值的解决方案。

【讨论】:

  • MisaGH 提供了发生这种情况的原因,但我认为这个游标解决方案提供了一个很好的内存效率解决方法,我猜这就是人们首先使用块的原因
【解决方案3】:

假设您正在使用块方法删除所有记录。该表有 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

然后块方法的另一个查询是:

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

【讨论】:

  • 这是实际的答案,并解释了为什么首先会出现问题。 Didier 目前接受的答案只是一个 hacky 解决方法。
  • 太棒了!谢谢!
  • 对于那些在chunkById() 不是返回所有结果时会遇到问题的人-你不应该使用自定义orderBy()chunkById(),它会破坏逻辑,因为 laravel 使用以下查询select * from some_records where id &gt; ? and deleted_at is null order by id asc limit 100
  • Misagh 描述得很好。非常感谢。
猜你喜欢
  • 1970-01-01
  • 2020-08-04
  • 1970-01-01
  • 2021-12-15
  • 2016-06-09
  • 1970-01-01
  • 1970-01-01
  • 2018-04-13
  • 1970-01-01
相关资源
最近更新 更多