Утечка памяти в php/symfony/doctrine?
У меня проблемы с пакетной вставкой объектов в базу данных с использованием Symfony 1.4 и Doctrine 1.2.
В моей модели есть объект определенного типа под названием "Сектор", каждый из которых имеет несколько объектов типа "Купо" (обычно в диапазоне от 50 до 200000). Эти объекты довольно маленькие; просто короткая строка идентификатора и одно или два целых числа. Всякий раз, когда группа секторов создается пользователем, мне нужно автоматически добавлять все эти экземпляры "Cupo" в базу данных. На случай, если что-то пойдет не так, я использую транзакцию доктрины для отката всего. Проблема в том, что я могу создать только около 2000 экземпляров, прежде чем php не хватит памяти. В настоящее время он имеет ограничение в 128 МБ, которого должно быть более чем достаточно для обработки объектов, использующих менее 100 байтов. Я пытался увеличить лимит памяти до 512 МБ, но php все еще падает, и это не решает проблему. Я правильно делаю пакетную вставку или есть лучший способ?
Вот ошибка:
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 71 bytes) in /Users/yo/Sites/grifoo/lib/vendor/symfony/lib/log/sfVarLogger.class.php on line 170
И вот код:
public function save($conn=null){
$conn=$conn?$conn:Doctrine_Manager::connection();
$conn->beginTransaction();
try {
$evento=$this->object;
foreach($evento->getSectores() as $s){
for($j=0;$j<$s->getCapacity();$j++){
$cupo=new Cupo();
$cupo->setActivo($s->getActivo());
$cupo->setEventoId($s->getEventoId());
$cupo->setNombre($j);
$cupo->setSector($s);
$cupo->save();
}
}
$conn->commit();
return;
}
catch (Exception $e) {
$conn->rollback();
throw $e;
}
Еще раз, этот код прекрасно работает для менее 1000 объектов, но все, что больше 1500, дает сбой. Спасибо за помощь.
9 ответов
Пробовал делать
$cupo->save();
$cupo->free();
$cupo = null;
(Но заменяя мой код) И я все еще получаю переполнения памяти. Любые другие идеи, так?
Обновить:
Я создал новую среду в моем database.yml, которая выглядит следующим образом:
all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=.......'
username: .....
password: .....
profiler: false
Запись profiler: false отключает ведение журнала запросов доктрины, в котором обычно хранится копия каждого сделанного вами запроса. Это не остановило утечку памяти, но я смог получить вдвое больший объем импорта данных, чем без него.
Обновление 2
я добавил
Doctrine_Manager::connection()->setAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS, true );
перед выполнением моих запросов, и изменил
$cupo = null;
в
unset($cupo);
И теперь мой сценарий радостно отрабатывает. Я почти уверен, что на этот раз он закончится без использования ОЗУ.
Обновление 3
Ага. Это выигрышная комбинация.
Я только что сделал "демонизированный" скрипт с symfony 1.4 и установка следующего остановила захват памяти:
sfConfig::set('sf_debug', false);
Для задачи Symfony я также столкнулся с этой проблемой и сделал следующие вещи. Это сработало для меня.
Отключить режим отладки. Добавьте следующее перед инициализацией соединения с БД
sfConfig::set('sf_debug', false);
Установить свободный атрибут объекта автоматического запроса для соединения с БД
$connection->setAttribute(Doctrine_Core::ATTR_AUTO_FREE_QUERY_OBJECTS, true );
Освободить весь объект после использования
$object_name->free()
Сбросить все массивы после использования
unset($array_name)
- Проверьте все запросы доктрины, используемые в задаче. Освободите все запросы после использования.
$q->free()
(Это хорошая практика для любого времени использования запроса.)
Это все. Надеюсь, это может кому-то помочь.
Утечка доктрины, и вы ничего не можете с этим поделать. Убедитесь, что вы используете $q->free(), когда это применимо, чтобы минимизировать эффект. Доктрина не предназначена для сценариев обслуживания. Единственный способ обойти эту проблему - разбить ваш скрипт на части, которые будут выполнять часть задачи. Один из способов сделать это - добавить параметр запуска к вашему сценарию, и после того, как определенное количество объектов было обработано, сценарий перенаправляет на себя с более высоким начальным значением. Это хорошо работает для меня, хотя и делает написание сценариев обслуживания более обременительным.
Для меня, я только что инициализировал задачу так:
// initialize the database connection
$databaseManager = new sfDatabaseManager($this->configuration);
$connection = $databaseManager->getDatabase($options['connection'])->getConnection();
$config = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', true);
sfContext::createInstance($config);
(С PROD CONFIG)
и использовать free() после save() для объекта доктрины
память стабильна на 25Mo
memory_get_usage=26.884071350098Mo
с PHP 5.3 на сжатие Debian
Пробовать unset($cupo);
после каждого сохранения. Это должно быть помочь. Другое дело - разделить скрипт и выполнить некоторую пакетную обработку.
Попробуйте сломать циклическую ссылку, которая обычно вызывает утечки памяти при
$cupo->save();
$cupo->free(); //this call
как описано в руководстве Доктрины.
Периодически закрывайте и снова открывайте соединение - не знаю, почему, но кажется, что PDO сохраняет ссылки.
То, что работает для меня, вызывает free
метод как это:
$cupo->save();
$cupo->free(true); // free also the related components
unset($cupo);