首页 > 解决方案 > Doctrine 批量插入 - 如何使用 Doctrine / Symfony 4 通过批量插入修复“内存不足”

问题描述

当我尝试从供应商处获取元数据时,我将数据转换为我们自己的元数据格式。但由于导入数据的庞大规模,应用程序会收到 OutOfMemoryException。

我尝试了几件事。就像抽出可能使用的内存一样,我也尝试使用Doctrine Batch Processing但这种方法存在一个小问题。Doctrine 数据处理基于带有索引的“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,我确实感觉批处理方法无法正常工作。

我想降低内存使用率,以便应用程序正常工作,或者希望得到一些建议来尝试找到解决此问题的其他方法。就像制作一个只处理后台导入的后台进程一样。

标签: phpdoctrinesymfony4bulkinsert

解决方案


您编写嵌套foreach循环的方式显然会成倍地消耗资源。我也怀疑它不会实现你真正想要的,因为你会有很多重复Supplier的 s 和Categorys。

在教义中使用完整的实体也会带来巨大的开销,但它确实有一些优势,所以我假设你想要这样做。

我对这种批量进口的方法是自下而上地工作。在您的情况下,它可能是我下面的变体。假设您在现有数据库中拥有数据,并且旧数据库中的每个现有“实体”都将拥有自己唯一的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- 对单个项目重复上述操作,只是这次保存链接的类别。

显然,有很多调整可以改善这一点。要点是,触摸每个供应商一次,然后触摸每个类别一次,然后按顺序触摸每个项目一次,这将比尝试在深度嵌套循环中解决要快几个数量级且资源消耗更少。


推荐阅读