5

运行这个简单的脚本时,我得到了下面发布的输出。这让我觉得我的代码或 Zend Framework/Magento 堆栈中存在内存泄漏。迭代任何类型的 Magento 集合时会出现此问题。我有什么遗漏或做错了吗?

脚本:

$customersCollection = Mage::getModel('customer/customer')->getCollection();

foreach($customersCollection as $customer) {
  $customer->load();
  $customer = null;
  echo memory_get_usage(). "\n";
}

输出:

102389104
102392920
...
110542528
110544744
4

5 回答 5

7

您的问题是,当您可以通过集合查询加载必要的数据时,每次迭代都会发出相当昂贵的查询:

$collection = Mage::getResourceModel('customer/customer_collection')->addAttributeToSelect('*');

将做同样的事情,但都在一个查询中。这种方法的警告是,如果有任何自定义事件观察者customer_load_beforecustomer_load_after事件(这些没有核心观察者),则需要为每个数据模型手动运行观察者。

编辑:归功于osonodoar发现了不正确的类引用(客户/客户与客户/客户集合)

于 2013-04-13T18:38:03.817 回答
3

一个对象(或其他值)的内存只有在 PHP 进程中的任何地方都没有对它的引用时才能被释放。在您的情况下,该行$customer = null仅将对该对象的引用数减少一,但不会使其达到零。

如果你考虑一个更简单的循环,这可能会变得更清楚:

$test = array('a' => 'hello');
foreach ( $test as $key => $value )
{
    // $value points at the same memory location as $test['a']
    // internally, that "zval" has a "refcount" of 2

    $value = null;
    // $value now points to a new memory location, but $test['a'] is unnaffected
    // the refcount drops to 1, but no memory is freed
}

因为您使用的是对象,所以有一个额外的转折 - 您可以在循环内修改对象而无需创建它的新副本:

$test = array('a' => new __stdClass);
// $test['a'] is an empty object

foreach ( $test as $key => $value )
{
    // $value points at the same object as $test['a']
    // internally, that object has a "refcount" of 2

    $value->foo = "Some data that wasn't there before";
    // $value is still the same object as $test['a'], but that object now has extra data
    // This requires additional memory to store that object

    $value = null;
    // $value now points to a new memory location, but $test['a'] is unnaffected
    // the refcount drops to 1, but no memory is freed
}

// $test['a']->foo now contains the string assigned in the loop, consuming extra memory

在您的情况下,该->load()方法可能会$customersCollection依次扩展每个成员中的数据量,每个成员都需要更多内存。$customersCollection在循环之前和之后检查可能会证实这一点。

于 2013-04-13T16:17:27.850 回答
0

首先,当取消设置变量时,使用 unset($variable) 而不是 $variable=null。它基本上做同样的事情,但你的意图要清楚得多。

其次,PHP 注定要死掉——内存泄漏不是一个大问题,因为 PHP 请求可能会持续几秒钟,然后进程就会死掉,并且它正在使用的所有内存都被释放以供下一个请求使用。除非您遇到扩展问题,否则无需担心。

编辑:这并不是说不要担心代码的质量,但是对于这样的事情,除非它引起问题,否则很可能不值得努力阻止它发生。

于 2013-04-13T16:53:46.003 回答
0

处理内存泄漏的其他方法是在循环内调用 exec 并让该 exec 函数完成导致内存泄漏的工作部分。

因此,一旦它完成了它的部分并终止了该 exec 中的所有内存泄漏,就会被释放。

因此,随着大量迭代,这种不断增加的内存损失将得到处理。

于 2013-04-13T19:41:36.780 回答
0

@benmarks 响应在这里是正确的方法,因为在循环中调用 load() 是一个非常昂贵的调用。

调用 $customer->load() 将增量分配将由 $customersCollection 引用的内存,直到循环结束时才会释放该内存。

但是,如果出于某种原因需要调用 load(),下面的代码不会泄漏内存,因为 GC 会在每次迭代中释放模型分配的所有内存。

$customersCollection = Mage::getModel('customer/customer')->getCollection();

foreach($customersCollection as $customer) {
    $customerCopy = Mage::getModel('customer/customer')->load($customer->getId());

    //Call to $customerCopy methods

    echo memory_get_usage(). "\n";
}
于 2013-04-15T17:14:13.747 回答