Foreach() 和 each() 内存不足,分块不起作用

Posted

技术标签:

【中文标题】Foreach() 和 each() 内存不足,分块不起作用【英文标题】:Foreach() and each() running out of memory, chunking not working 【发布时间】:2018-04-28 01:26:16 【问题描述】:

我正在编写一个工匠控制台命令,该命令循环遍历表中的所有记录并在该表上重新生成一个字段。

该字段是hash,生成为特定字符串的md5()

最初我的代码如下所示:

// Get all recipes
$recipes = Recipe::all();

$hashProgress = $this->output->createProgressBar(count($recipes));

// Loop over each recipe and generate a new hash for it
foreach ($recipes as $recipe)

    $hashString = '';

    $hashString .= $recipe->field1;
    $hashString .= $recipe->field2;
    $hashString .= $recipe->field3;
    $hashString .= $recipe->field4;
    $hashString .= $recipe->field5;
    $hashString .= $recipe->field6;
    $hashString .= $recipe->field7;

    $extras1Total = $recipe->extras1->sum('amount');
    $hashString .= $recipe->extras1->reduce(function ($str, $item) use ($extras1Total) 
        return $str . $item->name . ($extras1Total == 0 ? $item->amount : ($item->amount / $extras1Total * 100));
    , '');

    $extras2Total = $recipe->extras2->sum('amount');
    $hashString .= $recipe->extras2->reduce(function ($str, $item) use ($extras2Total) 
        return $str . $item->name . ($extras2Total == 0 ? $item->amount : ($item->amount / $extras2Total * 100));
    , '');

    $extras3Total = $recipe->extras3->sum('amount');
    $hashString .= $recipe->extras3->reduce(function ($str, $item) use ($extras3Total) 
        return $str . $item->name . ($extras3Total == 0 ? $item->amount : ($item->amount / $extras3Total * 100));
    , '');

    $extras4Total = $recipe->extras4->sum('amount');
    $hashString .= $recipe->extras4->reduce(function ($str, $item) use ($extras4Total) 
        return $str . $item->name . ($extras4Total == 0 ? $item->amount : ($item->amount / $extras4Total * 100));
    , '');

    $recipe->update([
        'hash' => md5($hashString),
    ]);

    $hashProgress->advance();


$hashProgress->finish();
$this->info(' Recipe hashes regenerated.');

在达到 28,000 条记录中的大约 10,000 条后,它会因内存耗尽错误而死:

php 致命错误:允许的内存大小为 268435456 字节已用尽(尝试分配 4096 字节)

我认为chunking 这可能会有所帮助:

// Get all recipes
$recipes = Recipe::all();

$hashProgress = $this->output->createProgressBar(count($recipes));

// Loop over each recipe and generate a new hash for it
foreach ($recipes->chunk(1000) as $chunk)

    foreach ($chunk as $recipe)
    
        $hashString = '';

        $hashString .= $recipe->field1;
        $hashString .= $recipe->field2;
        $hashString .= $recipe->field3;
        $hashString .= $recipe->field4;
        $hashString .= $recipe->field5;
        $hashString .= $recipe->field6;
        $hashString .= $recipe->field7;

        $extras1Total = $recipe->extras1->sum('amount');
        $hashString .= $recipe->extras1->reduce(function ($str, $item) use ($extras1Total) 
            return $str . $item->name . ($extras1Total == 0 ? $item->amount : ($item->amount / $extras1Total * 100));
        , '');

        $extras2Total = $recipe->extras2->sum('amount');
        $hashString .= $recipe->extras2->reduce(function ($str, $item) use ($extras2Total) 
            return $str . $item->name . ($extras2Total == 0 ? $item->amount : ($item->amount / $extras2Total * 100));
        , '');

        $extras3Total = $recipe->extras3->sum('amount');
        $hashString .= $recipe->extras3->reduce(function ($str, $item) use ($extras3Total) 
            return $str . $item->name . ($extras3Total == 0 ? $item->amount : ($item->amount / $extras3Total * 100));
        , '');

        $extras4Total = $recipe->extras4->sum('amount');
        $hashString .= $recipe->extras4->reduce(function ($str, $item) use ($extras4Total) 
            return $str . $item->name . ($extras4Total == 0 ? $item->amount : ($item->amount / $extras4Total * 100));
        , '');

        $recipe->update([
            'hash' => md5($hashString),
        ]);

        $hashProgress->advance();
    


$hashProgress->finish();
$this->info(' Recipe hashes regenerated.');

但我仍然收到内存耗尽错误。

如何在不增加内存限制的情况下遍历所有这些记录并实现我的目标?

【问题讨论】:

你指的是recipe下的一堆函数,我怀疑你那里有内存泄漏,寻找公共变量。 【参考方案1】:

你“分块”的方式实际上比初始代码消耗更多的内存。

您所做的是一次获取所有记录,将它们存储在$recipes,然后通过调用chunk() on the resulted collection 对结果进行分块。

相反,您需要在底层Recipe 模型的查询构建器上调用具有相同名称chunk() 的方法并逐块生成哈希:

Recipe::chunk(1000, function ($recipies) 
    // Hash generation logic here
);

通过这种方式,您消除了巨大的$recipes 变量,我确信这是这里的瓶颈。根据可用内存,您可能需要稍微调整块大小以避免内存耗尽。

另外,我会尝试在生成哈希时使用更少的变量,而不是留下$extras1Totalextras2Total、...变量的痕迹。所有这些都可以替换为$total,它将被一遍又一遍地重写。这是微优化。

附注如果数据库写入压力很大(总共 28k 很少见),您可能需要考虑一次(或几次)进行最终更新,而不是每条记录都进行。

【讨论】:

完美,我已按照您的建议进行了更改;通过在模型上直接使用chunk(),然后还用一个刚刚被重复使用的$total 替换了单个总数。关于进行大规模更新而不是按记录进行。我将如何解决这个问题,只需设置哈希值并在最后批量更新集合? 我建议仅在需要时才这样做。 YAGNI 可能适用。做bulk inserts 很容易。但是,要进行批量更新,您需要稍微更改代码以使用transactions。阅读更多关于它的信息here。另外,看看这个little awesome package 只需一个大查询即可。

以上是关于Foreach() 和 each() 内存不足,分块不起作用的主要内容,如果未能解决你的问题,请参考以下文章

$.each()与forEach()的区别,伪数组是啥

$.each()和$().each(),以及forEach()的用法

$.each()和$().each(),以及forEach()的用法

jquery foreach和each的区别

你知道forEach和each的区别吗?

$.each()和$().each(),以及forEach()的用法