Foreach () и каждому () не хватает памяти, блок не работает

Я пишу консольную консольную команду, которая перебирает все записи в таблице и восстанавливает поле в этой таблице.

Поле является 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.');

После получения примерно 10000 из 28000 записей он умрет с ошибкой исчерпания памяти:

Неустранимая ошибка PHP: допустимый объем памяти 268435456 байт исчерпан (попытался выделить 4096 байт)

я думал chunkЭто может помочь:

// 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.');

Но я все еще получаю ошибку исчерпания памяти.

Как я могу перебрать все эти записи и достичь того, что я хочу, не увеличивая лимит памяти?

2

Решение

То, как вы «разбиваете», на самом деле потребляет больше памяти, чем исходный код.

То, что вы делаете, это получение всех записей однажды, хранить их в $recipes а затем разбить результаты на части, вызвав chunk() на приведенной коллекции.

Вместо этого вам нужно вызвать метод с тем же именем, chunk(), на основе Recipe построитель запросов модели и генерирование хешей по частям:

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

Таким образом, вы избавляетесь от огромного $recipes Переменная, которая, я уверен, является узким местом здесь. В зависимости от доступной памяти вам может потребоваться немного изменить размер чанка, чтобы избежать ее исчерпания.

Кроме того, я бы попытался использовать меньше переменных при генерации хеша, а не оставлять след $extras1Total, extras2Total… переменные. Все они могут быть заменены на $total это будет переписано снова и снова. Это микрооптимизация.

Постскриптум В случае значительного стресса при записи в базу данных (что редко встречается при общем количестве 28 тыс.), Вы можете рассмотреть возможность сделать последние обновления за один (или несколько) шагов вместо того, чтобы делать это для каждой записи.

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]