在循环中执行 Doctrine 查询时的内存泄漏
Posted
技术标签:
【中文标题】在循环中执行 Doctrine 查询时的内存泄漏【英文标题】:Memory leak when executing Doctrine query in loop 【发布时间】:2014-12-24 08:39:59 【问题描述】:我无法在脚本中找到内存泄漏的原因。我有一个简单的存储库方法,它将我的实体中的“计数”列增加 X 数量:
public function incrementCount($id, $amount)
$query = $this
->createQueryBuilder('e')
->update('MyEntity', 'e')
->set('e.count', 'e.count + :amount')
->where('e.id = :id')
->setParameter('id', $id)
->setParameter('amount', $amount)
->getQuery();
$query->execute();
问题是,如果我在循环中调用它,每次迭代时内存使用量都会激增:
$entityManager = $this->getContainer()->get('doctrine')->getManager();
$myRepository = $entityManager->getRepository(MyEntity::class);
while (true)
$myRepository->incrementCount("123", 5);
$doctrineManager->clear();
gc_collect_cycles();
我在这里缺少什么?根据 Doctrine 的 advice on batch processing,我已经尝试过 ->clear()
。我什至尝试过gc_collect_cycles()
,但问题仍然存在。
我在 php 5.5 上运行 Doctrine 2.4.6。
【问题讨论】:
在你的函数中尝试作为最后一行$query->clear();
你将无限循环使用 while(true);是否有休息、返回或退出某处?
@Mihai 这只是给出了一个Call to undefined method Doctrine\ORM\Query::clear()
错误。
@HalayemAnis 是的,我只是将其从示例中删除以保持我的问题简单。
我认为您可以通过配置与数据库的持久连接来解决您的问题
【参考方案1】:
我遇到过类似的内存泄漏问题。我在 Symfony 5.2 项目中运行 Doctrine。更具体地说,我构建了一个永无止境的命令,它处理一个表中的条目,从另一个表中检索条目,并在其他表中创建 2 个新条目。 (事件处理)
我分两步解决了我的泄漏问题。
-
我在运行命令时使用
--no-debug
(正如 Jonathan 已经建议的那样)
我在循环末尾添加了$this->entityManager->clear();
为了查看和识别泄漏,我使用以下行来输出当前的内存使用情况:
$output->writeln('Memory Usage in MB: ' . memory_get_usage() / 1024 / 1024);
也许这有助于任何仍在与泄漏作斗争的人。
【讨论】:
【参考方案2】:我刚刚遇到了同样的问题,这些是为我解决的问题:
--无调试
正如 OP 在他们的回答中提到的,设置 --no-debug
(例如:php app/console <my_command> --no-debug
)对于 Symfony 控制台命令中的性能/内存至关重要。在使用 Doctrine 时尤其如此,因为没有它,Doctrine 将进入调试模式,这会消耗大量额外的内存(每次迭代都会增加)。有关更多信息,请参阅 Symfony 文档 here 和 here。
--env=prod
您还应该始终指定环境。默认情况下,Symfony 使用dev
环境来执行控制台命令。 dev
环境通常没有针对内存、速度、cpu 等进行优化。如果您想迭代数千个项目,您可能应该使用prod
环境(例如:php app/console <my_command> --env prod
)。请参阅here 和here 了解更多信息。
提示:我创建了一个名为console
的环境,我专门为运行控制台命令配置了该环境。这是关于how to create additional Symfony environments的信息。
php -d memory_limit=YOUR_LIMIT
如果运行大更新,您可能应该选择可接受的内存消耗量。如果您认为可能存在泄漏,这一点尤其重要。您可以使用php -d memory_limit=x
(例如:php -d memory_limit=256M
)为命令指定内存。注意:您可以将限制设置为-1
(通常是 php cli 的默认值)以让命令在没有内存限制的情况下运行,但这显然很危险。
用于批处理的格式良好的控制台命令
使用上述提示运行大更新的格式良好的控制台命令如下所示:
php -d memory_limit=256M app/console <acme>:<your_command> --env=prod --no-debug
使用 Doctrine 的 IterableResult
在循环中使用 Doctrine 的 ORM 时,另一个重要的问题是使用 Doctrine 的 IterableResult(参见 Doctrine Batch Processing docs)。这在提供的示例中无济于事,但通常在进行这样的处理时,它会超出查询的结果。
定期刷新
如果您正在做的部分工作是对数据进行更改,则应定期刷新,而不是在每次迭代时刷新。冲洗既昂贵又缓慢。刷新的频率越低,命令完成的速度就越快。但是请记住,Doctrine 会将未刷新的数据保存在内存中。因此,您刷新的频率越低,您需要的内存就越多。
您可以使用类似以下的方法每 100 次迭代刷新一次:
if ($count % 100 === 0)
$this->em->flush();
还要确保在循环结束时再次刷新(用于刷新最后
输出内存使用情况
跟踪您的命令在运行时消耗了多少内存非常有帮助。您可以通过输出 PHP 内置的 memory_get_usage() 函数返回的响应来做到这一点。
祝你好运!
【讨论】:
对我来说 $this->entityManager->getConnection()->getConfiguration()->setSQLLogger(null);修复了问题【参考方案3】:对我来说,这是清除学说,或者如文档所述,分离所有实体:
$this->em->clear(); //Here em is the entity manager.
所以在我的循环中,每 1000 次迭代刷新一次并分离所有实体(我不再需要它们):
foreach ($reader->getRecords() as $position => $value)
$this->processValue($value, $position);
if($position % 1000 === 0)
$this->em->flush();
$this->em->clear();
$this->progress->advance();
希望这会有所帮助。
PS:here's the documentation.
【讨论】:
当您没有复杂的引用并且不创建连接到单个对象的实体时,这就像魅力一样。但在清除后,如果这足以进行批处理,您可能总是重新创建参考或部分参考。 这是我所有问题的解决方案。而且我只读取数据,从不写入任何数据。$this->em->clear(MyClass::class);
有助于仅清除类的实体,因为我想在管理器中保留一些其他类。如其他解决方案中所述,我还需要 --no-debug。【参考方案4】:
Doctrine 会记录您所做的任何查询。如果您进行大量查询(通常发生在循环中),Doctrine 可能会导致巨大的内存泄漏。
您需要禁用 Doctrine SQL Logger 来克服这个问题。
我建议仅对循环部分执行此操作。
在循环之前,获取当前记录器:
$sqlLogger = $em->getConnection()->getConfiguration()->getSQLLogger();
然后禁用 SQL Logger:
$em->getConnection()->getConfiguration()->setSQLLogger(null);
在这里循环:
foreach() / while() / for()
循环结束后,放回Logger:
$em->getConnection()->getConfiguration()->setSQLLogger($sqlLogger);
【讨论】:
这就是--no-debug
选项正在做的事情。【参考方案5】:
我通过在我的命令中添加 --no-debug
解决了这个问题。事实证明,在调试模式下,分析器将有关每个查询的信息存储在内存中。
【讨论】:
每个学说查询都会增加大约 4k 的内存使用量。这为我解决了问题。 我应该在哪里添加--no-debug
?能具体说明一下吗?
***.com/a/10913115/3757139 展示了如何使用 $em->getConnection()->getConfiguration()->setSQLLogger(null);
以编程方式禁用 SQL 日志记录【参考方案6】:
每次迭代都在浪费内存。更好的方法是准备查询一次并交换参数多次。例如:
class MyEntity extends EntityRepository
private $updateQuery = NULL;
public function incrementCount($id, $ammount)
if ( $this->updateQuery == NULL )
$this->updateQuery = $this->createQueryBuilder('e')
->update('MyEntity', 'e')
->set('e.count', 'e.count + :amount')
->where('e.id = :id')
->getQuery();
$this->updateQuery->setParameter('id', $id)
->setParameter('amount', $amount);
->execute();
正如您所提到的,您可以在此处使用批处理,但先尝试一下,看看性能如何(如果有的话)...
【讨论】:
感谢您的回答,但不幸的是,这对内存泄漏没有影响。我预计这是因为$updateQuery
变量已经被垃圾收集器清理了。尽管如此,您的建议应该会使事情变得更快,所以我会实施它。还有其他想法吗?!
我没想到会这样。我们在这里谈论多少次迭代?
最多 10,000 次迭代。
好的,我认为这需要原生查询(通过普通的Connection
对象)。看到这个:***.com/questions/3325012/…以上是关于在循环中执行 Doctrine 查询时的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章