У меня возникли проблемы с поиском причины утечки памяти в моем скрипте. У меня есть простой метод репозитория, который увеличивает столбец «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()
, согласно совету Doctrine по пакетной обработке . Я даже пробовал 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(); } }
Как вы упомянули, вы можете использовать пакетную обработку здесь, но сначала попробуйте это и посмотрите, насколько хорошо (если вообще) выполняет …
Я просто столкнулся с тем же вопросом, это то, что исправило это для меня:
Как указывает OP в своем ответе, установка --no-debug
(ex: 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
(ex: 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 ). Это не поможет в приведенном примере, но обычно при выполнении такой обработки это результат запроса.
Может быть очень полезно отслеживать, сколько памяти потребляет ваша команда во время ее работы. Это можно сделать, выведя ответ, возвращаемый встроенной функцией памяти_get_usage () PHP.
Удачи!
Doctrine ведет журналы любого запроса, который вы делаете. Если вы делаете много запросов (обычно это происходит в циклах), Doctrine может вызвать огромную утечку памяти.
Чтобы преодолеть это, вам нужно отключить Doctrine SQL Logger.
Я рекомендую делать это только для части цикла.
Перед циклом получить текущий регистратор:
$sqlLogger = $em->getConnection()->getConfiguration()->getSQLLogger();
А затем отключите SQL Logger:
$ Em-> GetConnection () -> getConfiguration () -> setSQLLogger (нуль);
Делайте петлю здесь: foreach() / while() / for()
После окончания цикла верните Logger:
$em->getConnection()->getConfiguration()->setSQLLogger($sqlLogger);
Для меня это очищало доктрину или, как говорится в документации, отделяя все сущности:
$this->em->clear(); //Here em is the entity manager.
Таким образом, внутри моей петли y сбрасывает каждые 1000 итераций и отделяет все сущности (мне они больше не нужны):
foreach ($reader->getRecords() as $position => $value) { $this->processValue($value, $position); if($position % 1000 === 0){ $this->em->flush(); $this->em->clear(); } $this->progress->advance(); }
-foreach ($reader->getRecords() as $position => $value) { $this->processValue($value, $position); if($position % 1000 === 0){ $this->em->flush(); $this->em->clear(); } $this->progress->advance(); }
Надеюсь это поможет.
PS: вот документация .