在循环中执行Doctrine查询时内存泄漏

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文档。

--env =产品

您还应该始终指定环境。默认情况下,Symfony将dev环境用于控制台命令。该dev环境通常未针对内存,速度,CPU等进行优化。如果要迭代数千个项目,则可能应该使用该prod环境(例如:)php app/console <my_command> --env prod。有关更多信息,请参见此处此处

提示:我创建了一个console专门配置用于运行控制台命令的环境。这是有关如何创建其他Symfony环境的信息

php -d memory_limit = YOUR_LIMIT

如果运行较大的更新,则可能应该选择可以使用多少内存。如果您认为可能存在泄漏,这尤其重要。您可以使用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

使用学说的IterableResult

在循环中使用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()函数返回的响应来做到这一点。

祝好运!

  • 对我来说 $this-&gt;entityManager-&gt;getConnection()-&gt;getConfiguration()-&gt;setSQLLogger(null); 解决了问题 (3认同)

Jon*_*han 16

我通过添加--no-debug到我的命令解决了这个问题.事实证明,在调试模式下,探查器正在存储有关每个查询的内存信息.

  • 每个学说查询都增加了大约4k的内存使用量.这为我修好了. (2认同)
  • http://stackoverflow.com/a/10913115/3757139 展示了如何使用`$em-&gt;getConnection()-&gt;getConfiguration()-&gt;setSQLLogger(null);`以编程方式禁用SQL日志记录。 (2认同)

amc*_*ror 9

对我来说,这是清除学说,或者如文档所说,分离所有实体:

$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:这是文档


Jov*_*vic 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();
    }
}
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);

  • 这就是 `--no-debug` 选项正在做的事情。 (3认同)