PHP和do/while循环内存泄漏问题

4
我有一个do/while循环,遍历数据库行。因为它运行了很多天,处理了数十万行,所以内存消耗很重要,否则它会崩溃。现在,每次迭代都会将约4kb的内存添加到脚本的内存使用中。我使用memory_get_usage()来监视使用情况。
我在每次迭代中首先取消设置循环中使用的每个变量,因此我真的不知道我还能做什么。我的猜测是,do/while会在每次迭代中收集一些数据,这就是消耗4kb内存的原因。我知道4kb听起来不像什么,但当你有数十万次迭代时,它很快就会累加。
有人能建议另一种遍历大量数据库行的方法或如何消除这种“内存泄漏”吗?
编辑:以下是更新后的循环代码。上面只有几个require_once()。
$URLs = new URLs_url(db());
$c = new Curl;
$c->headers = 1;
$c->timeout = 60;
$c->getinfo = true;
$c->follow = 0;
$c->save_cookies = false;

do {
    // Get url that hasn't been checked for a week
    $urls = null;

    // Check week old
    $urls = $URLs->all($where)->limit(10);

    foreach($urls as $url) {
        #echo date("d/m/Y h:i").' | Checking '.$url->url.' | db http_code: '.$url->http_code;

        // Get http code    
        $c->url = $url->url;
        $data = $c->get();

        #echo ' - new http_code: '.$data['http_code'];

        // Save info
        $url->http_code = $data['http_code'];
        $url->lastchecked = time();
        $URLs->save($url);
        $url = null;
        #unset($c);
        $data = null;
        #echo "\n".memory_get_usage().' | ';
        echo "\nInner loop memory usage: ".memory_get_usage();
    }
    echo "\nOuter loop memory usage: ".memory_get_usage();

} while($urls);

以下是两个循环中内存消耗情况的一些日志:

Inner loop memory usage: 611080
Inner loop memory usage: 612452
Inner loop memory usage: 613788
Inner loop memory usage: 615124
Inner loop memory usage: 616460
Inner loop memory usage: 617796
Inner loop memory usage: 619132
Inner loop memory usage: 620500
Inner loop memory usage: 621836
Inner loop memory usage: 623172
Outer loop memory usage: 545240
Inner loop memory usage: 630680
Inner loop memory usage: 632016
Inner loop memory usage: 633352
Inner loop memory usage: 634688
Inner loop memory usage: 636088
Inner loop memory usage: 637424
Inner loop memory usage: 638760
Inner loop memory usage: 640096
Inner loop memory usage: 641432
Inner loop memory usage: 642768
Outer loop memory usage: 556392
Inner loop memory usage: 640416
Inner loop memory usage: 641752
Inner loop memory usage: 643088
Inner loop memory usage: 644424
Inner loop memory usage: 645760
Inner loop memory usage: 647096
Inner loop memory usage: 648432
Inner loop memory usage: 649768
Inner loop memory usage: 651104
Inner loop memory usage: 652568
Outer loop memory usage: 567608
Inner loop memory usage: 645924
Inner loop memory usage: 647260
Inner loop memory usage: 648596
Inner loop memory usage: 649932
Inner loop memory usage: 651268
Inner loop memory usage: 652604
Inner loop memory usage: 653940
Inner loop memory usage: 655276
Inner loop memory usage: 656624
Inner loop memory usage: 657960
Outer loop memory usage: 578732

3
通常情况下,循环本身不会泄露内存,而是循环中的某些内容会造成泄漏。请提供一些代码以便我们帮助诊断真正的问题。 - Mark Elliot
这是代码。希望它有所帮助。 - James D52
代码再次更新以匹配当前代码。 - James D52
4个回答

2

这一部分应该只在循环之前发生:

$c = new Curl;
$c->headers = 1;
$c->timeout = 60;
...
$c->getinfo = true;
$c->follow = 0;
$c->save_cookies = false;

编辑:哦,整个内容都包含在一个do/while循环中。/facepalm

编辑2:还有这个重要的部分:

unset($class_object)不会释放对象分配的资源。如果在创建和销毁对象的循环中使用,可能会很容易地导致资源问题。为了避免这个问题,请显式调用析构函数。

http://www.php.net/manual/en/function.unset.php#98692

编辑3:

这是什么?难道不能将其移动到循环外面吗?

$URLs = new URLs_url(db());

编辑4:

暂时尝试移除这些行。

    $url->http_code = $data['http_code'];
    $url->lastchecked = time();
    $URLs->save($url);

这是一个 do/while 循环。Foreach 循环只是为了批量处理事务。而且在循环之前我无法真正执行 Curl,因为 foreach 的每次迭代都会检查 10 个不同的 URL。 - James D52
@James D52 但是你可以在每个迭代之前配置 curl 而不是创建一个新实例。这些值在迭代之间是否会改变?似乎不会。URL 会变化,来自该 URL 的响应也会变化。但是,并不一定需要为每个迭代创建一个新的 curl 实例。 - George Marian
好的,我把其他所有东西都移到了do之前。现在每个循环中只有$c->url = $url->url & $c->get()。什么也没改变。我还是在某个地方泄漏了大约1.5kb :/ - James D52
请查看我上面放置的日志。那里没有date()函数,但它仍在增长。我希望它本应该是date()函数 :/ - James D52
@James D52 在这个阶段,我会尽量将其简化到最基本的程度,即使这意味着你不能保存所需保存的信息。我会在我的答案中发布我将删除的三行代码,以进行此测试。 - George Marian
显示剩余10条评论

0

问题可能出在这里

$c = new Curl

在循环外部实例化Curl一次,然后在内部保持重复使用相同的实例是否可能。如果需要,您可以在循环中将所有字段重置为null。

我曾经遇到过类似的问题。取消设置不起作用 - 结果垃圾回收是垃圾。当我重复使用对象时,它很好(好吧,由于不同的原因而破坏了,所以最终我重新实现了Java)。


1
@Michale Jones,请看一下PHP手册中关于unset()的这个注释:http://www.php.net/manual/en/function.unset.php#98692 - George Marian

0

这可能对你有帮助,也可能没有。早在2000年,我有一个客户,他的互联网速度非常慢,想要在本地更新所有网站内容管理系统,并在完成后更新到线上。当时在win xp上的IIS上,我找不到一种方法来增加脚本超时时间,只能60秒,通常需要2分钟才能完成更新,因此显然会超时。

为了解决这个问题,我会让脚本更新一定数量的行,这些行可以保证在一分钟内安全执行,然后使用继续更新的参数调用自身,以此类推,直到更新完所有行。也许你可以尝试类似的解决方案来解决你的情况?

也许在调用自身之前运行一段固定的时间,或者在你的情况下,检查内存并在使用过高时重定向?

我使用了类似于以下的代码:

脚本顶部:

$started = microtime(true);

然后在你的循环中加入:

if((microtime(true)-$started) > ($seconds_to_redirect)) {
    //call script with parameter
}

这就是我能想到的全部。


0

我认为你的核心问题在于你只清除了外部循环中的内容。

例如,$c = new Curl将为内部循环的每次迭代分配堆内存,但你只unset了最后一个实例。我建议在内部循环的结尾unset任何可以清除的东西(如$c$data)。


通过将未设置的变量放在最后,我成功地将内存泄漏从4kb减少到了约1.5kb。不知道还有什么泄漏的 :/ - James D52
@James,把调用memory_get_usage()的位置移到unset($URLs)之后,我有一种感觉你会发现1.5kb的增长与$URLs->save()的调用有关。 - Mark Elliot
什么也没做。内存消耗仍然随着每次迭代增加约1.5kb。 - James D52
db()用于连接MySQL。我正在使用phpDataMapper类。顺便说一句,这是一个很棒的类 :) $URLs = new URLs_url(db());现在已经移出了两个循环,因此它只运行一次。 - James D52
@James,你能展示一下在每次外部循环迭代中内存使用情况吗? - Mark Elliot
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接