Я пишу консольную консольную команду, которая перебирает все записи в таблице и восстанавливает поле в этой таблице.
Поле является 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.');
Но я все еще получаю ошибку исчерпания памяти.
Как я могу перебрать все эти записи и достичь того, что я хочу, не увеличивая лимит памяти?
То, как вы «разбиваете», на самом деле потребляет больше памяти, чем исходный код.
То, что вы делаете, это получение всех записей однажды, хранить их в $recipes
а затем разбить результаты на части, вызвав chunk()
на приведенной коллекции.
Вместо этого вам нужно вызвать метод с тем же именем, chunk()
, на основе Recipe
построитель запросов модели и генерирование хешей по частям:
Recipe::chunk(1000, function ($recipies) {
// Hash generation logic here
});
Таким образом, вы избавляетесь от огромного $recipes
Переменная, которая, я уверен, является узким местом здесь. В зависимости от доступной памяти вам может потребоваться немного изменить размер чанка, чтобы избежать ее исчерпания.
Кроме того, я бы попытался использовать меньше переменных при генерации хеша, а не оставлять след $extras1Total
, extras2Total
… переменные. Все они могут быть заменены на $total
это будет переписано снова и снова. Это микрооптимизация.
Постскриптум В случае значительного стресса при записи в базу данных (что редко встречается при общем количестве 28 тыс.), Вы можете рассмотреть возможность сделать последние обновления за один (или несколько) шагов вместо того, чтобы делать это для каждой записи.
Других решений пока нет …