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

时间:2014-10-28 19:27:01

标签: php symfony memory-leaks doctrine-orm doctrine

我在查找脚本中的内存泄漏原因时遇到了问题。我有一个简单的存储库方法,增加了一个'计数'我的实体中的列数量为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();
}

问题是,如果我在循环中调用它,则每次迭代都会占用内存使用量:

$doctrineManager = $this->getContainer()->get('doctrine')->getManager();
$myRepository = $doctrineManager->getRepository('MyEntity');
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。

6 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分:9)

我刚遇到同样的问题,这些是为我解决的问题:

- 无调试

正如他们在答案中提到的那样,设置--no-debug(例如:php app/console <my_command> --no-debug)对于Symfony控制台命令中的性能/内存至关重要。使用Doctrine时尤其如此,因为没有Doctrine,Doctrine将进入调试模式,这会消耗大量的额外内存(每次迭代都会增加)。有关详细信息,请参阅Symfony文档herehere

- ENV = PROD

您还应始终指定环境。默认情况下,Symfony使用dev环境进行控制台命令。 dev环境通常不针对内存,速度,CPU等进行优化。如果要迭代数千个项目,则应该使用prod环境(例如:{{1 }})。有关详细信息,请参阅herehere

提示:我创建了一个名为php app/console <my_command> --env prod的环境,我专门为运行控制台命令而配置了这个环境。以下是有关how to create additional Symfony environments

的信息

php -d memory_limit = YOUR_LIMIT

如果运行大更新,您应该选择可以使用的内存量。如果您认为可能存在泄漏,这一点尤为重要。您可以使用console(例如php -d memory_limit=x)为Command指定内存。注意:您可以将限制设置为php -d memory_limit=256M(通常是php cli的默认值),让命令在没有内存限制的情况下运行,但这显然很危险。

用于批处理的成型控制台命令

使用上述提示运行大更新的格式良好的控制台命令如下所示:

-1

使用Doctrine的IterableResult

在循环中使用Doctrine的ORM时,另一个巨大的问题是使用Doctrine的IterableResult(参见Doctrine Batch Processing docs)。这在所提供的示例中没有帮助,但通常在进行此类处理时,它会超出查询结果。

定期刷新

如果您正在做的部分工作是对数据进行更改,则应定期刷新而不是每次迭代。冲洗是昂贵且缓慢的。你冲洗的次数越少,命令完成的速度就越快。但请记住,Doctrine会将未刷新的数据保存在内存中。因此,刷新的频率越低,您需要的内存就越多。

您可以使用以下内容来刷新每100次迭代:

php -d memory_limit=256M app/console <acme>:<your_command> --env=prod --no-debug

还要确保在循环结束时再次刷新(用于刷新最后的&lt; 100条目)。

随时输出内存使用量

跟踪命令在运行时消耗的内存量非常有用。您可以通过输出PHP内置的memory_get_usage()函数返回的响应来实现。

祝你好运!

答案 2 :(得分:4)

您每次迭代都会浪费内存。更好的方法是准备查询一次并多次交换参数 。例如:

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();
    }
}

正如你所提到的,你可以在这里使用批量处理,但首先尝试一下,看看有多好(如果有的话)......

答案 3 :(得分:4)

Doctrine会记录您所做的任何查询。如果你进行大量的查询(通常在循环中发生),Doctrine会导致巨大的内存泄漏。

您需要禁用Doctrine SQL Logger来克服这个问题。

我建议仅对循环部分执行此操作。

在循环之前,获取当前记录器:

$sqlLogger = $em->getConnection()->getConfiguration()->getSQLLogger();

然后禁用SQL Logger:

  

$ EM-&GT;的getConnection() - &GT; getConfiguration() - &GT; setSQLLogger(空);

在这里循环: foreach() / while() / for()

循环结束后,放回记录器:

$em->getConnection()->getConfiguration()->setSQLLogger($sqlLogger);

答案 4 :(得分: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

答案 5 :(得分:2)

我遇到了类似的内存泄漏问题。我在 Symfony 5.2 项目中运行 Doctrine。更具体地说,我构建了一个永无止境的命令,它处理一个表中的条目,从另一个表中检索条目,并在其他表中创建 2 个新条目。 (事件处理)

我分两步解决了我的泄漏问题。

  1. 我在运行命令时使用 --no-debug(正如 Jonathan 已经建议的那样)
  2. 我在循环末尾添加了 $this->entityManager->clear();

为了查看和识别泄漏,我使用以下行输出当前内存使用情况:

$output->writeln('Memory Usage in MB: ' . memory_get_usage() / 1024 / 1024);

也许这有助于任何仍在与泄漏作斗争的人。