У меня проблемы с поиском причины утечки памяти в моем скрипте. У меня есть простой метод репозитория, который увеличивает столбец «count» в моей сущности на X:
public function incrementCount($id, $amount)
{
$query = $this
->createQueryBuilder('e')
->update('MyEntity', 'e')
->set('e.count', 'e.count + :amount')
->where('e.id = :id')
->setParameter('id', $id)
->setParameter('amount', $amount)
->getQuery();
$query->execute();
}
Проблема в том, что если я вызову это в цикле, то использование памяти будет появляться на каждой итерации:
$doctrineManager = $this->getContainer()->get('doctrine')->getManager();
$myRepository = $doctrineManager->getRepository('MyEntity');
while (true) {
$myRepository->incrementCount("123", 5);
$doctrineManager->clear();
gc_collect_cycles();
}
Что мне здесь не хватает? я пробовал ->clear()
согласно доктрине консультации по пакетной обработке. Я даже пытался gc_collect_cycles()
, но проблема остается.
Я использую Doctrine 2.4.6 на PHP 5.5.
Я решил это, добавив --no-debug
к моей команде. Оказывается, в режиме отладки профилировщик хранил информацию о каждом запросе в памяти.
Вы тратите память на каждую итерацию. Гораздо лучше было бы подготовить запрос один раз и поменять аргументы много раз. Например:
class MyEntity extends EntityRepository{
private $updateQuery = NULL;
public function incrementCount($id, $ammount)
{
if ( $this->updateQuery == NULL ){
$this->updateQuery = $this->createQueryBuilder('e')
->update('MyEntity', 'e')
->set('e.count', 'e.count + :amount')
->where('e.id = :id')
->getQuery();
}
$this->updateQuery->setParameter('id', $id)
->setParameter('amount', $amount);
->execute();
}
}
Как вы упомянули, вы можете использовать здесь пакетную обработку, но сначала попробуйте это и посмотрите, насколько хорошо (если вообще) выполняется …
Я просто столкнулся с той же проблемой, вот что исправило это для меня:
Как ОП упомянул в своем ответе, настройка --no-debug
(Например: php app/console <my_command> --no-debug
) имеет решающее значение для производительности / памяти в консольных командах Symfony. Это особенно верно при использовании Doctrine, так как без него Doctrine перейдет в режим отладки, который потребляет огромное количество дополнительной памяти (которая увеличивается на каждой итерации). Смотрите документы Symfony Вот а также Вот для получения дополнительной информации.
Вы также должны всегда указывать среду. По умолчанию Symfony использует dev
среда для консольных команд. dev
среда обычно не оптимизирована для памяти, скорости, процессора и т. д. Если вы хотите перебрать более тысячи элементов, вам, вероятно, следует использовать prod
среда (напр .: php app/console <my_command> --no-debug
). Увидеть Вот а также Вот для получения дополнительной информации.
Совет: я создал среду под названием console
что я специально настроил для запуска консольных команд. Вот информация о как создать дополнительные среды Symfony.
Если вы запускаете большое обновление, вам, вероятно, следует выбрать объем памяти, который он может использовать. Это особенно важно, если вы думаете, что может быть утечка. Вы можете указать память для Команды, используя php -d memory_limit=x
(Например: php -d memory_limit=256M
). Примечание: вы можете установить ограничение на -1
(обычно используется по умолчанию для php cli), чтобы команда запускалась без ограничения памяти, но это, очевидно, опасно.
Правильно сформированная консольная команда для запуска большого обновления с использованием приведенных выше советов будет выглядеть так:
php -d memory_limit=256M app/console <acme>:<your_command> --env=prod --no-debug
Еще один важный момент, когда ORM Doctrine используется в цикле, — это использование IterableResult от Doctrine (см. Doctrine Batch Processing docs). В приведенном примере это не поможет, но обычно при такой обработке результаты заканчиваются в результате запроса.
Может быть очень полезно отслеживать, сколько памяти использует ваша команда во время ее работы. Вы можете сделать это путем вывода ответа, возвращаемого встроенным PHP memory_get_usage () функция.
Удачи!
Doctrine ведет логи любого вашего запроса. Если вы делаете много запросов (обычно это происходит в циклах), Doctrine может вызвать огромную утечку памяти.
Вам нужно отключить Doctrine SQL Logger, чтобы преодолеть это.
Я рекомендую делать это только для части цикла.
Перед циклом, получите текущий регистратор:
$sqlLogger = $em->getConnection()->getConfiguration()->getSQLLogger();
А затем отключите регистратор SQL:
$ Em-> GetConnection () -> getConfiguration () -> setSQLLogger (нуль);
Сделайте цикл здесь:
foreach() / while() / for()
После окончания цикла верните Logger:
$em->getConnection()->getConfiguration()->setSQLLogger($sqlLogger);
Для меня это было прояснение доктрины или, как сказано в документации, отделение всех сущностей:
$this->em->clear(); //Here em is the entity manager.
Итак, внутри моего цикла вы сбрасываете каждые 1000 итераций и отсоединяете все сущности (они мне больше не нужны):
foreach ($reader->getRecords() as $position => $value) {
$this->processValue($value, $position);
if($position % 1000 === 0){
$this->em->flush();
$this->em->clear();
}
$this->progress->advance();
}
Надеюсь это поможет.
PS: вот документация.