Проблема с транзакцией Doctrine, использующей слишком много памяти

#php #orm #symfony1 #doctrine

#php #orm #symfony1 #доктрина

Вопрос:

Я продолжаю получать эту ошибку при запуске одного из моих скриптов;

Фатальная ошибка PHP: разрешенный объем памяти в 1073741824 байта исчерпан (пытался выделить 71 байт) … lib/symfony-1.4.11/lib/plugins/sfDoctrinePlugin/lib/vendor/doctrine/Doctrine/Connection/Statement.php в строке 246, …

Ниже приведена урезанная версия скрипта, который вызывает ошибку;

 public function executeImportFile(sfWebRequest $request)
{
 ini_set('memory_limit', '1024M');
 set_time_limit ( 0 );

 //more codes here...

 $files = scandir($workspace.'/'.$directory);

 foreach ($files as $file) {
   $path = $workspace.'/'.$directory.'/'.$file;

   if ($file != "." amp;amp; $file != "..") {
     $this->importfile($path);
   }
 }
}


protected function importfile($path){


 $connection =
sfContext::getInstance()->getDatabaseManager()->getDatabase('doctrine')->getDoctrineConnection();
 $connection->beginTransaction();
 try {

   //more codes here...


   while ($data = $reader->read()) //reads each line of a csv file
   {
     // send the line to another private function to be processed
     // and then write to database
     $this->writewave($data);
   }


   $connection->commit();

 } catch (Exception $e) {
   $connection->rollback();
 }
}
  

В основном скрипт читает все CSV-файлы в
папка (которая содержит десятки тысяч строк каждая), обработайте ее,
и запишите это в базу данных, используя транзакцию Doctrine.

Хотя я не думаю, что мне нужно устанавливать лимит памяти и ограничение по времени в обеих функциях, скрипт завершается, поскольку Doctrine использует весь выделенный 1 ГБ памяти.

Обычно она останавливается после обработки 10 файлов, а выделение большего объема памяти позволит ей обработать немного больше файлов и все равно приведет к сбою.

Есть ли что-то, чего мне здесь не хватает, из-за чего память не освобождается после обработки каждого файла?

Мохд Шакир Закария http://www.mohdshakir.net

Ответ №1:

Старайтесь освобождать любые объекты везде, где только можете, включая объекты запросов / подключений, особенно когда они находятся внутри циклов.

http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/improving-performance.html#free-objects

Комментарии:

1. Спасибо, я попытался освободить объекты, и это решает мою проблему 🙂

2. Кто-нибудь пробовал компилировать Doctrine из Symfony? есть какие-либо указания на такое решение

3. @Prasad Я опаздываю на вечеринку, но я оставлю ответ для дальнейшего использования. В Symfony «unset» не будет работать. Вы должны отсоединить свои сущности от EntityManager с помощью $em->detach() , за которой следует $em->clear() , а затем заставить сборщик мусора PHP работать с gc_enable(); и gc_collect_cycles() это особенно полезно, когда вы используете doctrine в цикле. Просто будьте осторожны с отображенными и обратными отношениями, потому что использование $em->detach() или $em->clear() слишком рано вызовет каскадные проблемы с вашими объектами!

Ответ №2:

Doctrine использует заведомо много памяти при работе с большими наборами данных — импорт больших / множественных файлов таким образом невозможен.

Даже если вы импортируете каждый файл в отдельном вызове функции, doctrine имеет внутренний объектный кэш, поэтому они не освобождаются.

Вашим лучшим вариантом было бы немного изменить задачу, чтобы она принимала filename в качестве параметра. Если она передана, обработайте только этот файл (надеясь, что он не станет слишком большим). Если имя файла не передано, оно перебирает все файлы, как сейчас, и вызывает себя через exec , так что это другой процесс, и память действительно освобождается.

Комментарии:

1. Я собираюсь сделать что-то похожее на OP — я действительно не вижу необходимости в Doctrine — столкнусь ли я с проблемами при использовании подключения PDO?

2. Нет, у PDO нет объектного кэша, как у doctrine.

3. Спасибо… Так, может быть, это вариант для OP?

4. Зависит от того, нужна ли ему бизнес-логика, содержащаяся в его моделях, определенно нет. В общем, лучшим решением является разные процессы через каждые несколько тысяч записей.

Ответ №3:

самая большая проблема, которую я вижу в вашем скрипте, заключается в том, что вы очень часто вызываете sfcontext. IMHO sfcontext не является синглтоном, что означает, что вы создаете новый экземпляр в каждом цикле. не могли бы вы передать соединение в метод?

 public function executeImportFile(sfWebRequest $request)
{
 ini_set('memory_limit', '1024M');
 set_time_limit ( 0 );


$connection = sfContext::getInstance()->getDatabaseManager()->getDatabase('doctrine')->getDoctrineConnection();

 //more codes here...

 $files = scandir($workspace.'/'.$directory);

 foreach ($files as $file) {
   $path = $workspace.'/'.$directory.'/'.$file;

   if ($file != "." amp;amp; $file != "..") {
     $this->importfile($path, $connection);
   }
 }
}


protected function importfile($path, $connection){
 $connection->beginTransaction();
 try {

   while ($data = $reader->read()) //reads each line of a csv file
   {
     $this->writewave($data);
   }
  $connection->commit();

 } catch (Exception $e) {
   $connection->rollback();
 }
}
  

Комментарии:

1. sfContext является синглтоном, см. Источник sfContext