我刚刚遇到了同样的问题,这些是为我解决它的事情:
--无需调试
正如OP在他们的答案中提到的,设置(例如:)对于Symfony控制台命令中的性能/内存至关重要。在使用 Doctrine 时尤其如此,因为没有它,Doctrine 将进入调试模式,这会消耗大量的额外内存(每次迭代都会增加)。有关详细信息,请参阅此处和此处的 Symfony 文档。--no-debug
php bin/console <my_command> --no-debug
--env=prod
还应始终指定环境。默认情况下,Symfony 将环境用于控制台命令。该环境通常未针对内存,速度,CPU等进行优化。如果要循环访问数千个项目,则可能应该使用环境(例如:)。有关详细信息,请参阅此处和此处。dev
dev
prod
php bin/console <my_command> --env prod
提示: 我创建了一个名为“我”的环境,该环境是我专门为运行控制台命令而配置的。以下是有关如何创建其他 Symfony 环境的信息。console
php -d memory_limit=YOUR_LIMIT
如果运行大型更新,则可能应该选择可接受的内存量以供其使用。如果您认为可能存在泄漏,这一点尤其重要。您可以使用 (例如:) 指定命令的内存。注意:您可以将限制设置为(通常是php cli的默认值)以使命令在没有内存限制的情况下运行,但这显然是危险的。php -d memory_limit=x
php -d memory_limit=256M
-1
用于批处理的格式正确的控制台命令
使用上述提示运行大型更新的格式良好的控制台命令如下所示:
php -d memory_limit=256M bin/console <acme>:<your_command> --env=prod --no-debug
使用学说的可迭代结果
在循环中使用 Doctrine 的 ORM 时,另一个重要的问题是使用 Doctrine 的 IterableResult(参见 Doctrine Batch Processing 文档)。这在提供的示例中无济于事,但通常在进行这样的处理时,它是查询的结果。
定期冲洗
如果您正在执行的部分操作是对数据进行更改,则应定期刷新,而不是在每次迭代时刷新。冲洗既昂贵又缓慢。刷新的频率越低,命令完成的速度就越快。但是,请记住,该教义会将未刷新的数据保存在内存中。因此,刷新的频率越低,所需的内存就越多。
您可以使用类似下面的内容每 100 次迭代刷新一次:
if ($count % 100 === 0) {
$this->em->flush();
}
还要确保在循环结束时再次刷新(用于刷新最后< 100 个条目)。
清除实体管理器
您可能还希望在冲洗后清除:
$this->em->flush();
$em->clear(); // Detach ALL objects from Doctrine.
或
$this->em->flush();
$em->clear(MyEntity::class); // Detach all MyEntity from Doctrine.
$em->clear(MyRelatedEntity::class); // Detach all MyRelatedEntity from Doctrine.
随时输出内存使用情况
跟踪命令在运行时消耗的内存量会很有帮助。您可以通过输出 PHP 内置的 memory_get_usage() 函数返回的响应来做到这一点。
$output->writeln(memory_get_usage());
例
$memUse = round(memory_get_usage() / 1000000, 2).'MB';
$this->output->writeln('Processed '.$i.' of '.$totalCount.' (mem: '.$memUse.')');
滚动您自己的批次
滚动自己的批次也可能有所帮助。您可以通过使用开始和限制来执行此操作,就像使用分页一样。我能够仅使用90Mb的RAM处理400万行。
下面是一些示例代码:
protected function execute(InputInterface $input, OutputInterface $output) {
/* ... */
$totalCount = $this->getTotalCount();
$batchSize = 10000;
$i = 0;
while ($i < $totalCount) {
$i = $this->processBatch($i, $batchSize, $totalCount);
}
/* ... */
}
private function processBatch(int $start, int $limit, int $totalCount): int {
/* @var $q \Doctrine\ORM\Query */
$q = $this->em->createQueryBuilder()
->select('e')
->from('AcmeExampleBundle:MyEntity', 'e')
->setFirstResult($start)
->setMaxResults($limit)
->getQuery();
/* @var $iterableResult \Doctrine\ORM\Internal\Hydration\IterableResult */
$iterableResult = $q->iterate(null, \Doctrine\ORM\Query::HYDRATE_SIMPLEOBJECT);
$i = $start;
foreach ($iterableResult as $row) {
/* @var $myEntity \App\Entity\MyEntity */
$myEntity = $row[0];
$this->processOne($myEntity);
if (0 === ($i % 1000)) {
$memUse = round(memory_get_usage() / 1000000, 2).'MB';
$this->output->writeln('Processed '.$i.' of '.$totalCount.' (mem: '.$memUse.')');
}
$this->em->detach($row[0]);
$i++;
}
return $i;
}
private function processOne(MyEntity $myEntity): void {
// Do entity processing here.
}
private function getTotalCount(): int {
/* @var $q \Doctrine\ORM\Query */
$q = $this->em
->createQueryBuilder()
->select('COUNT(e.id)')
->from('AcmeExampleBundle:MyEntity', 'e')
->getQuery();
$count = $q->getSingleScalarResult();
return $count;
}
祝你好运!