【问题标题】:Doctrine findBy() out of memory error教义 findBy() 内存不足错误
【发布时间】:2012-05-25 15:05:59
【问题描述】:

我有一个表,其中包含大约 30 万行描述 Apple 推送通知服务的设备。我使用 Doctrine 2 作为 ORM。

插入设备没有问题,但是,检索它们是完全不同的故事。使用简单的 MySQL SELECT 我可以在几秒钟内获得它们,而 WiFi 是主要瓶颈。但是,如果我尝试通过 Doctrine 获取它们,即使我允许 PHP 高达 1 GB,它也会耗尽内存。根据文档,我已经为 Doctrine 实体创建了 getter 和 setter 以及受保护的属性。

我不知道我做错了什么。这很好:

$devices = mysql_query("SELECT * FROM `Devices` WHERE `deviceProperty`='someValue'");

$message = new Message();
while($device = mysql_fetch_array($devices))
{
    $message->addRecipient($device['pushToken']);
}

但这会在第一行耗尽内存(它永远不会在下一行到达断点):

$devices = self::$entityManager->getRepository('Device')->findBy(array("deviceProperty" => "someValue"));
$message = new Message();
foreach($devices as $device)
{
    $message->addRecipient($device->getPushToken);
}

【问题讨论】:

  • 那是因为你在内存中创建了 300k 个实体实例。
  • 每个实体实例消耗超过 4(四个!)MB 的内存?那么延迟加载有什么意义呢?在我看来,每个实例只有在您通过 getter 请求属性时才会被填充,就像 mysql 结果集一样。
  • 我很确定它延迟加载实体,而不是将映射到实体的数据。所以在某个地方你有 300k 元素数组,每个元素都有至少一个属性(PK)。并且每次你做$device->getPushToken 时都会水合到一个物体上。除非您专门销毁该对象,否则它仍在内存中,因此即使我对细节不正确,延迟加载也不适用对象水合,特别是如果您只需要 2 个属性。
  • 但即便如此,这可能会消耗大约 300 兆内存。我不希望这会用完千兆字节的内存。我真的很想知道如何和什么,如果我听起来脾气暴躁,很抱歉! :)

标签: php memory doctrine doctrine-orm


【解决方案1】:

您正在拉入 300k 个对象,它会消耗太多内存,请尝试分块处理...

$message = new Message();

$limit = 50;
$offset = 0;
while($devices = self::$entityManager->getRepository('Device')->findBy(array("deviceProperty" => "someValue"), array(), $limit, $offset))
{
   foreach($devices as $device)
   {
       $message->addRecipient($device->getPushToken);
   }
   $offset += $limit;
}

【讨论】:

  • 另外,您可以编写一个使用 Array hydrator 的自定义 DQL 函数,并具有与您的 mysql_query 示例类似的实现。
【解决方案2】:

如果您使用 DQL,您可以使用 iterate() 函数来遍历结果并在处理后刷新每个结果:

$message = new Message();
$cleaner = 0;

$q = self::$entityManager->createQuery(
    'SELECT d from Device d
     WHERE
     d.deviceProperty = :devicePropertyValue
    ');
$q->setParameter('devicePropertyValue', 'someValue');
//$q->setFirstResult(10000);
//$q->setMaxResults(5000);

$devices = $q->iterate();

foreach ($devices as $row) {
    $device = $row[0];

    $message->addRecipient($device->getPushToken);

    self::$entityManager->detach($device);

    if ($cleaner++ > 100) {
        self::$entityManager->clear();
        $cleaner = 0;
    }

}

对于我正在运行的批量重新索引作业,这将我的内存需求从 3GB 降低到了 256M 以下。

根据 Doctrine documentation,“使用 fetch-join 集合值关联的查询无法迭代结果” - 我认为这意味着内置方法,例如 findBy()。

注意注释掉的 setFirstResult() 和 setMaxResults() 可用于偏移和限制。

【讨论】:

    猜你喜欢
    • 2012-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多