Doctrine大容量插入-如何使用Doctrine / Symfony 4通过大容量插入修复“内存不足”

时间:2019-05-16 13:23:21

标签: php doctrine symfony4 bulkinsert

当我试图从供应商那里获取元数据时,我正在将数据转换为我们自己的元数据格式。但是由于导入数据的绝对大小,应用程序将获得OutOfMemoryException。

我尝试了几件事。就像增加可能使用的内存一样,我也尝试使用Doctrine Batch Processing,但是这种方法存在一个小问题。原则数据处理基于带有索引的“ for”循环。

$batchSize = 20;
for ($i = 1; $i <= 10000; ++$i) {
    $user = new CmsUser;
    $user->setStatus('user');
    $user->setUsername('user' . $i);
    $user->setName('Mr.Smith-' . $i);
    $em->persist($user);
    if (($i % $batchSize) === 0) {
        $em->flush();
        $em->clear(); // Detaches all objects from Doctrine!
    }
}
$em->flush(); //Persist objects that did not make up an entire batch
$em->clear();

但是我导入的数据是我在三维“ foreach”循环中创建的多层数组:

$this->index = 0;
$batchSize = 100;
foreach ($response as $item) {
    $item = new Item;
    $item->setName($item->name);
    $item->setStatus($item->status);
    $em->persist($item);
    if (($this->index % $batchSize) === 0) {
        $em->flush();
        $em->clear();
    }
    foreach ($item->category as $category) {
        $category = new Category;
        $category->setName($category->name);
        $category->setStatus($category->status);
        $em->persist($item);
        if (($this->index % $batchSize) === 0) {
            $em->flush();
            $em->clear();
        }
        foreach ($category->suppliers as $supplier) {
            $supplier = new Supplier;
            $supplier->setName($supplier->name);
            $supplier->setStatus($supplier->status);
            $em->persist($item);
            if (($this->index % $batchSize) === 0) {
                $em->flush();
                $em->clear();
            }
        }
    }
}
$this->em->flush();

这是虚构的代码,用来说明我的问题。有了这个,应用程序仍然会出现OutOfMemoryException异常,我确实感觉到批处理方法无法正常工作。

我想降低内存使用率,以便应用程序正常运行,或者想要一些建议以尝试找到解决此问题的其他方法。就像进行后台处理一样,只需要在后台进行导入即可。

1 个答案:

答案 0 :(得分:0)

编写嵌套foreach循环的方式显然会成倍地消耗资源。我还怀疑它无法实现您真正想要的功能,因为您会有很多重复的SupplierCategory

在理论上使用完整实体也会带来大量开销,但是它确实具有一些优势,因此我假设您要这么做。

我对这样的大宗进口商品采取的方法是从下至上进行工作。在您的情况下,它可能是我下面的内容的变体。假定您在现有数据库中有数据,并且旧数据库中的每个现有“实体”将具有其自己唯一的id

1-将所有供应商从旧数据库导入新数据库;新数据库中有一个名为oldId的列,该列引用了旧数据库中的唯一id。停止以清除缓存/内存。

2-将所有供应商从新数据库中拉入以其oldId索引的数组。我使用如下代码:

$suppliers = [];
$_suppliers = $this->em->getRepository(Supplier:class)->findAll();
foreach ($_suppliers as $supplier) {
    $suppliers[$supplier->getOldId()] = $supplier;
}

3-对类别重复步骤1。在导入期间,您的旧数据库将引用链接的供应商的oldId。尽管您的代码没有执行此操作,但是我假设您想维护供应商与类别之间的链接,因此您现在可以在链接的“旧”供应商上的循环中按其oldId来引用供应商:

$category->addSupplier($suppliers[ <<oldSupplier Id>> ]);

4-对单个项目重复上述操作,只有这次保存链接的类别。

很明显,有很多调整可以改善这一点。主要要点是,一次接触每个供应商,一次接触每个类别,然后依次接触每个项目一次,比尝试在一个深层嵌套的循环中处理要快几个数量级,而且资源消耗更少。