【问题标题】:How can I prevent memory leak in csv file import / isbn lookup script如何防止 csv 文件导入/isbn 查找脚本中的内存泄漏
【发布时间】:2010-09-23 17:25:30
【问题描述】:

我的网站允许用户上传包含书籍列表的 csv 文件。然后该脚本读取该文件并使用 PEAR Services_Amazon 类针对 Amazon 检查 isbn 编号,返回增强的图书数据。但是,每当我在书籍列表上运行脚本时,消耗的内存量都会稳步增加,直到出现致命错误。目前,分配了 32 MB,我只能在 CSV 文件崩溃之前读取 370 条记录。

我有一个要导入 4500 条记录文件的用户和一个具有 256 MB RAM 的虚拟服务器,因此增加内存限制不是解决方案。

这是 CSV 导入的简化版本:

$handle = fopen($filename, "r");
 while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
 $isbn = $data[6];
 checkIsbn($isbn);
 }

这是该函数的精简版:

function checkIsbn($isbn) {     
 $amazon = &new Services_Amazon(ACCESS_KEY_ID, SECRET_KEY, ASSOC_ID);
 // -- $options array filled with $isbn, other requested info --
 $products = $amazon->ItemSearch('Books', $options);        
 // -- Then I create an array from the first result --
  $product = $products['Item'][0];
  $title = $product['ItemAttributes']['Title']; 
  // -- etc... various attributes are pulled from the $product array --
 mysql_query($sql); // -- put attributes into our DB
  unset($product); 
  unset($products);
  usleep(1800000); // maximum of 2000 calls to Amazon per hour as per their API
return $book_id;    
 }

我尝试过:在函数和 CSV 导入代码中取消设置数组并将它们设置为 NULL。我增加了所有超时时间以确保这不是问题。我安装了 xdebug 并运行了一些测试,但我发现每次访问 Amazon 类时脚本在内存中不断增加(我不是 xdebug 专家)。我在想,也许 Services_Amazon 类中的变量在每次运行时都没有被清除,但不知道从这里去哪里。我希望取消设置两个数组可以做到这一点,但没有运气。

编辑:更新:我认为这可能是 PEAR 类中的一个问题(并且查看此处与 PEAR 相关的一些问题,这似乎是可能的)。无论如何,目前我的 OOP 技能很少,所以我找到了一种方法来通过多次重新加载页面来做到这一点 - 有关详细信息,请参阅下面的答案。

【问题讨论】:

  • 我会检查是否可以在该类中找到任何缓存。同时:使用 `&new` 已经被弃用很长时间了,所以除非你还在使用 PHP4,否则我会放弃 &
  • 有缓存,但我相信它只是缓存了亚马逊的输出,所以你可以再次参考它。我只引用每条记录一次。

标签: php memory-leaks pear


【解决方案1】:

首先,这不是内存泄漏,而是糟糕的编程...... 第二点是 unset 不会释放使用的内存,它只是从当前作用域中删除对变量的引用。

最好不要在此处复制内存,而是通过仅分配对 $products 的引用来使 $produkt 和 $title 成为指针;

$product = &$products['Item'][0];
$title = &$product['ItemAttributes']['Title']; 

那么,不只是 unset() 做

$products = NULL;
unset($products);

这将释放内存,不是立即释放,而是在 php 垃圾收集器下次运行时...

为什么每次我调用该函数时都创建一个 Serverces_Amazon 的新实例?在构造对象时创建实例的类成员呢?

class myService
{
    protected $_service;

    public function __construct()
    {
        $this->_service = new Services_Amazon(ACCESS_KEY_ID, SECRET_KEY, ASSOC_ID);
    }

    public function checkIsbn($isbn)
    {
        //...
        $this->_service->ItemSearch('Books', $options);
        //...
    }
}

$myService = new myService;
$handle = fopen($filename, "r");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {

    $bookId = $myService->checkIsbn($data[6]);
}

此外,您还假设您的用户都使用相同的 CSV 格式,这不太可能……所以最好使用真正的 CSV 解析器,它可以处理所有可能的 CSV 符号……

【讨论】:

  • 糟糕的编程是我的中间名...感谢您的想法。我知道 unset 在这里并不完全正确。我会看看你建议的课程。至于 CSV,为简单起见,我删除了其他可以使这一点更清晰的代码——这部分脚本实际上是从一个已知的格式源中获取 CSV,该格式源不会改变。
  • 好吧.. 请告诉我们您现在可以导入多少。您可能还有其他浪费内存的循环等...
  • 一旦我弄清楚如何设置这个类,我会的。我对课程很陌生,有点困惑。如何从 $this->_service->ItemSearch 获取 $products 数组?
  • 在 checkIsbn 中,而不是:$products = $amazon->ItemSearch('Books', $options);使用:$products = $this->_service->ItemSearch('Books', $options);
  • @mandel,您可以简单地将我的代码示例用作您的课程的基础。注释行是您必须放置功能的地方。该示例的最后几行也适用于您发布的代码。
【解决方案2】:

只创建$amazon 对象的单个实例并将其传递给您的checkIsbn 函数怎么样?他们不需要创建 4500 个实例。

$amazon = &new Services_Amazon(ACCESS_KEY_ID, SECRET_KEY, ASSOC_ID);
$handle = fopen($filename, "r");
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
 $isbn = $data[6];
 checkIsbn($amazon, $isbn);
}
unset($amazon);

【讨论】:

  • 这给出了一个致命错误“Services_Amazon 类的对象无法转换为字符串”。
  • 您需要更改 checkIsbn 的方法签名以获取另一个参数。应该是:function checkIsbn($amazon, $isbn) {
  • 我重做了,这次没有报错;一定有错字,因为我确实添加了参数。唉,但这根本不会改变处理时间/内存问题。
【解决方案3】:

我认为您还应该研究如何连接到数据库 - 每次调用 checkIsbn 时您是否都在创建新连接?这也可能是问题的一部分。

【讨论】:

  • 页面开头有一个主mysql_connect/mysql_select_db;在循环中我只向数据库添加一条记录,所以我不相信这是一个新的连接,是吗?
猜你喜欢
  • 2010-12-20
  • 2011-08-09
  • 2012-03-30
  • 1970-01-01
  • 1970-01-01
  • 2010-09-21
  • 2016-07-10
  • 2011-12-05
相关资源
最近更新 更多