Jon*_*han 8 php memory-leaks doctrine symfony doctrine-orm
我在查找脚本中的内存泄漏原因时遇到了麻烦.我有一个简单的存储库方法,它将我的实体中的'count'列递增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();
}
Run Code Online (Sandbox Code Playgroud)
问题是,如果我在循环中调用它,则每次迭代都会占用内存使用量:
$doctrineManager = $this->getContainer()->get('doctrine')->getManager();
$myRepository = $doctrineManager->getRepository('MyEntity');
while (true) {
$myRepository->incrementCount("123", 5);
$doctrineManager->clear();
gc_collect_cycles();
}
Run Code Online (Sandbox Code Playgroud)
我在这里错过了什么?->clear()
根据Doctrine 关于批处理的建议,我试过了.我甚至尝试过gc_collect_cycles()
,但问题仍然存在.
我在PHP 5.5上运行Doctrine 2.4.6.
Col*_*wll 18
我遇到了同样的问题,这些是为我解决的问题:
正如OP在其答案中提到的那样,设置--no-debug
(ex php app/console <my_command> --no-debug
:)对于Symfony控制台命令中的性能/内存至关重要。当使用Doctrine时尤其如此,因为如果没有它,Doctrine将进入调试模式,这将消耗大量的额外内存(每次迭代都会增加)。有关更多信息,请参见此处和此处的Symfony文档。
您还应该始终指定环境。默认情况下,Symfony将dev
环境用于控制台命令。该dev
环境通常未针对内存,速度,CPU等进行优化。如果要迭代数千个项目,则可能应该使用该prod
环境(例如:)php app/console <my_command> --env prod
。有关更多信息,请参见此处和此处。
提示:我创建了一个console
专门配置用于运行控制台命令的环境。这是有关如何创建其他Symfony环境的信息。
如果运行较大的更新,则可能应该选择可以使用多少内存。如果您认为可能存在泄漏,这尤其重要。您可以使用php -d memory_limit=x
(ex:)指定命令的内存php -d memory_limit=256M
。注意:您可以将限制设置为-1
(通常是php cli的默认值),以使命令不受内存限制地运行,但这显然很危险。
使用上述技巧,格式正确的控制台命令可用于运行大型更新:
php -d memory_limit=256M app/console <acme>:<your_command> --env=prod --no-debug
在循环中使用Doctrine的ORM时,另一个巨大的优点是使用Doctrine的IterableResult(请参阅Doctrine批处理文档)。这在提供的示例中无济于事,但通常在进行此类处理时,它超出了查询的结果。
如果您所做的部分工作是对数据进行更改,则应定期刷新而不是在每次迭代时刷新。冲洗既昂贵又缓慢。刷新次数越少,命令完成的速度就越快。但是请记住,Doctrine会将未刷新的数据保存在内存中。因此,刷新的次数越少,所需的内存就越多。
您可以使用以下类似的方法每100次迭代刷新一次:
if ($count % 100 === 0) {
$this->em->flush();
}
Run Code Online (Sandbox Code Playgroud)
还请确保在循环结束时再次刷新(用于刷新最后的<100个条目)。
跟踪命令在运行时消耗了多少内存确实很有帮助。您可以通过输出PHP内置的memory_get_usage()函数返回的响应来做到这一点。
祝好运!
Jon*_*han 16
我通过添加--no-debug
到我的命令解决了这个问题.事实证明,在调试模式下,探查器正在存储有关每个查询的内存信息.
对我来说,这是清除学说,或者如文档所说,分离所有实体:
$this->em->clear(); //Here em is the entity manager.
Run Code Online (Sandbox Code Playgroud)
所以在我的循环中 y 每 1000 次迭代刷新一次并分离所有实体(我不再需要它们):
foreach ($reader->getRecords() as $position => $value) {
$this->processValue($value, $position);
if($position % 1000 === 0){
$this->em->flush();
$this->em->clear();
}
$this->progress->advance();
}
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助。
PS:这是文档。
每次迭代都在浪费内存。更好的方法是准备一次查询并多次交换参数。例如:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所提到的,您可以在此处使用批处理,但请先尝试一下,看看(如果有的话)性能如何……
小智 5
教义会记录您进行的任何查询的日志。如果您进行大量查询(通常在循环中发生),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);
归档时间: |
|
查看次数: |
6957 次 |
最近记录: |