我有一个Symfony命令,该命令使用PHP 7.0.22上的学说Paginator。该命令必须从大表格处理数据,因此我在100个项目的块中进行此操作。问题是,经过几百个循环,它填充了2.56亿RAM。作为针对OOM的措施(暂时),我使用:
-
$em->getConnection()->getConfiguration()->setSQLLogger(null);
-禁用SQL Logger,该记录器填充存储器的记录查询,用于运行许多SQL命令的脚本 -
$em->clear();
-在每个循环结束时将所有对象从学说中分离
我已经放了一些memory_get_usage()
的转储来检查正在发生的事情,看来收集器没有像命令在每个$paginator->getIterator()->getArrayCopy();
呼叫时那样清洁。
我什至尝试用gc_collect_cycles()
手动收集每个环的垃圾,但仍然没有区别,命令开始使用18m,每百个项目以约2m的速度增加。还试图手动拆除结果和查询构建器...什么都没有。我删除了所有数据处理,仅保留了选择查询和吊架并获得相同的行为。
任何人都知道我应该在哪里看?
注意: 256m应该足以容纳这种操作,因此请勿建议您建议增加允许内存的解决方案。
条纹下调的execute()
方法看起来像这样:
protected function execute(InputInterface $input, OutputInterface $output)
{
// Remove SQL logger to avoid out of memory errors
$em = $this->getEntityManager(); // method defined in base class
$em->getConnection()->getConfiguration()->setSQLLogger(null);
$firstResult = 0;
// Get latest ID
$maxId = $this->getMaxIdInTable('AppBundle:MyEntity'); // method defined in base class
$this->getLogger()->info('Working for max media id: ' . $maxId);
do {
// Get data
$dbItemsQuery = $em->createQueryBuilder()
->select('m')
->from('AppBundle:MyEntity', 'm')
->where('m.id <= :maxId')
->setParameter('maxId', $maxId)
->setFirstResult($firstResult)
->setMaxResults(self::PAGE_SIZE)
;
$paginator = new Paginator($dbItemsQuery);
$dbItems = $paginator->getIterator()->getArrayCopy();
$totalCount = count($paginator);
$currentPageCount = count($dbItems);
// Clear Doctrine objects from memory
$em->clear();
// Update first result
$firstResult += $currentPageCount;
$output->writeln($firstResult);
}
while ($currentPageCount == self::PAGE_SIZE);
// Finish message
$output->writeln("nn<info>Done running <comment>" . $this->getName() . "</comment></info>n");
}
记忆泄漏是由学说的Paginator生成的。我用准备好的语句用本机查询替换了它并修复了它。
您应该考虑的其他事情:
- 如果您要替换学说的编织者,则应通过在查询中添加限制来重建分页功能。
- 使用
--no-debug
标志或-env=prod
或两者都运行命令。问题是命令在dev
环境中默认运行。这使一些数据收集器可以在prod
环境中使用。在Symfony文档中查看有关此主题的更多信息 - 如何使用控制台
编辑:在我的特定情况下我还使用了实现HTTP Guzzle库的捆绑eightpoints/guzzle-bundle
(我的命令中有一些API调用)。这个捆绑包也泄漏了,显然是通过一些中间件。为了解决此问题,我必须独立实例化guzzle客户端,而无需八点捆绑。