php - 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,我确实感觉批处理方法无法正常工作。
我想降低内存使用率,以便应用程序正常工作,或者希望得到一些建议来尝试找到解决此问题的其他方法。就像制作一个只处理后台导入的后台进程一样。
解决方案
您编写嵌套foreach
循环的方式显然会成倍地消耗资源。我也怀疑它不会实现你真正想要的,因为你会有很多重复Supplier
的 s 和Category
s。
在教义中使用完整的实体也会带来巨大的开销,但它确实有一些优势,所以我假设你想要这样做。
我对这种批量进口的方法是自下而上地工作。在您的情况下,它可能是我下面的变体。假设您在现有数据库中拥有数据,并且旧数据库中的每个现有“实体”都将拥有自己唯一的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- 对单个项目重复上述操作,只是这次保存链接的类别。
显然,有很多调整可以改善这一点。要点是,触摸每个供应商一次,然后触摸每个类别一次,然后按顺序触摸每个项目一次,这将比尝试在深度嵌套循环中解决要快几个数量级且资源消耗更少。
推荐阅读
- javascript - Calling a javascript function inside another function is considered undefined
- flutter - 为什么在 Dart/Flutter 中将 Future 类称为 Future API?
- c# - 如何将 TryParse 与 Console.Readile 一起使用?
- java - 每次我从 spring.io 创建 Spring Boot 项目时都会创建 ServletInitializer 文件
- asynchronous - 如何让 Google 的 SpeechClient.StreamingRecognize.WriteAsync 更快?
- matlab - MATLAB MILP 优化问题与目标函数的求和以及受制于的方程
- r - 我想根据没有 rbind 的序列号连续修改几列,因为列标题不相同
- javascript - 如何在静态方法中调用实例方法?
- reactjs - 使用 ReactJS 让我的页面正确路由
- scala - Akka Streams 中的“conditionalZip”运算符