随着Doctrine批量插入,内存使用变得疯狂


Memory usage goes wild with Doctrine bulk insert

我正试图使用Doctrine2和Symfony2 fixture bundle在MySQL数据库中插入大量数据(30000多行)。

我找到了正确的方法。我看到了很多关于记忆泄露和条令的问题,但没有令人满意的答案。它通常是条令clear()函数。

所以,我做了各种形状的:

while (($data = getData()) {
    $iteration++;
    $obj = new EntityObject();
    $obj->setName('henry');
    // Fill object...
    $manager->persist($obj);
    if ($iteration % 500 == 0) {
        $manager->flush();
        $manager->clear();
        // Also tried some sort of:
        // $manager->clear($obj);   
        // $manager->detach($obj);
        // gc_collect_cycles();
    }
}

flush()之后,PHP内存仍然很乱(我确信这一点)。事实上,每次刷新实体时,内存都会根据批大小和实体增加一定的量,直到达到致命的允许内存大小耗尽错误。对于一个非常非常小的实体,它可以工作,但内存消耗增加太多:几个MB,而它应该是KB。

clear()detach()或调用GC似乎根本没有效果。它只清除一些KB。

我的方法有缺陷吗?我错过什么了吗?是虫子吗?

更多信息

  • 没有flush(),内存几乎不会移动
  • 降低批次不会改变结果
  • 数据来自需要净化的CSV

EDIT(部分解决方案)

@qooplmao带来了一个显著降低内存消耗的解决方案,禁用条令sql记录器:$manager->getConnection()->getConfiguration()->setSQLLogger(null);

然而,它仍然异常高,而且还在增加。

正如@Axalix所建议的那样,我使用这个资源解决了问题。

这就是我修改代码的方式:

// IMPORTANT - Disable the Doctrine SQL Logger
$manager->getConnection()->getConfiguration()->setSQLLogger(null);
// SUGGESION - make getData as a generator (using yield) to to save more memory.
while ($data = getData()) {
  $iteration++;
  $obj = new EntityObject();
  $obj->setName('henry');
  // Fill object...
  $manager->persist($obj);
  // IMPORTANT - Temporary store entities (of course, must be defined first outside of the loop)
  $tempObjets[] = $obj;
  if ($iteration % 500 == 0) {
    $manager->flush();
    // IMPORTANT - clean entities
    foreach($tempObjets as $tempObject) {
      $manager->detach($tempObject);
    }
    $tempObjets = null;
    gc_enable();
    gc_collect_cycles();
  }
}
// Do not forget the last flush
$manager->flush();

最后但同样重要的是,当我将此脚本与Symfony数据固定装置一起使用时,在命令中添加--no-debug参数也非常重要。那么内存消耗是稳定的。

我发现Doctrine在执行过程中记录所有SQL。我建议用下面的代码禁用它,它真的可以节省内存:

use Doctrine'ORM'EntityManagerInterface;

public function __construct(EntityManagerInterface $entity_manager)
{
    $em_connection = $entity_manager->getConnection();
    $em_connection->getConfiguration()->setSQLLogger(null);
}

我的建议是放弃批量插入的Doctrine方法。我真的很喜欢Doctrine,但我只是讨厌批量插件上的这种东西。

MySQL有一个很棒的东西叫做LOAD DATA。我宁愿使用它,或者即使我必须先清理我的csv,然后再加载。

如果需要更改值,我会将csv读取到数组$csvData = array_map("str_getcsv", file($csv));。更改数组中所需的内容,并将其保存到行中。之后,使用新的.csv与MySQL一起加载。

为了支持我关于为什么我不会在上面描述的这件事上使用教义的说法。