php脚本中的内存泄漏

时间:2010-04-08 14:29:52

标签: php mysql memory-leaks

我有一个运行mysql查询的php脚本,然后循环结果,并在该循环中还运行多个查询:

    $sqlstr = "SELECT * FROM user_pred WHERE uprType != 2 AND uprTurn=$turn ORDER BY uprUserTeamIdFK";
    $utmres = mysql_query($sqlstr) or trigger_error($termerror = __FILE__." - ".__LINE__.": ".mysql_error());
    while($utmrow = mysql_fetch_array($utmres, MYSQL_ASSOC)) {
// some stuff happens here    
//  echo memory_get_usage() . " - 1241<br/>\n";
        $sqlstr = "UPDATE user_roundscores SET ursUpdDate=NOW(),ursScore=$score WHERE ursUserTeamIdFK=$userteamid";
        if(!mysql_query($sqlstr)) {
            $err_crit++;
            $cLog->WriteLogFile("Failed to UPDATE user_roundscores record for user $userid - teamuserid: $userteamid\n");
            echo "Failed to UPDATE user_roundscores record for user $userid - teamuserid: $userteamid<br>\n";
            break;
        }
    unset($sqlstr);
    //  echo memory_get_usage() . " - 1253<br/>\n";
// some stuff happens here too
}

更新查询永远不会失败。

出于某种原因,在memory_get_usage的两次调用之间,添加了一些内存。因为大循环运行大约500.000次或更多次,最终它实际上增加了很多内存。这里有什么我想念的吗?
难道这可能是因为内存实际上并没有在两个调用之间添加,而是在脚本的另一个位置?

编辑:一些额外的信息: 循环之前它大约是5mb,循环之后大约440mb,每个更新查询增加大约250个字节。 (其余的内存将添加到循环中的其他位置)。 我没有发布更多“其他东西”的原因是因为它有大约300行代码。我发布了这个部分,因为它看起来是添加最多内存的地方。

6 个答案:

答案 0 :(得分:4)

如果内存泄漏只是一个问题,如果它以“内存耗尽”错误查杀脚本。 PHP将很乐意自己垃圾收集任何异常的对象/变量,但收集器不会踢,直到它必须 - 垃圾收集可能是一个非常昂贵的操作。

即使您经常重复使用相同的对象/变量,看到内存使用量也会增加是正常的 - 直到内存使用率超过某个级别,收集器才会启动并清理房屋。

我怀疑如果您将用户ID分组并发布更少的更新,每次更改更多记录,您可以使运行速度更快。例如执行以下操作:

UPDATE user_roundscores SET ursUpdDate=NOW() WHERE ursUserTeamIdFK IN (id1, id2, id3, id4, id5, etc...)

而不是每个用户更新一次。通过数据库接口层的往返次数减少,服务器上的时间减少=运行速度更快。

同样,请考虑现在将其扩展到数百万用户的影响,正如您在评论中所说的那样。一百万次单独更新将花费非常少的时间来运行,因此NOW()将不会是“常量”。如果完整运行需要5分钟,那么您将获得各种ursUpdDate个时间戳。您可能需要考虑在服务器端变量中缓存单个NOW()调用,并针对该变量发出更新:

 SELECT @cachednow :p NOW();
 UPDATE .... SET ursUpDate = @cachednow WHERE ....;

答案 1 :(得分:2)

最好的方法可能是获取所有userIds并将它们刷新到文件中。 然后运行一个新的脚本,用管道分叉x个工作无人机。然后给他们一个小的userIds列表,在他们完成每个列表时进行处理。使用多个cpus / cores / servers,您可以更快地完成任务。如果一个工人失败,只需启动一个新工人。 要将其他服务器用作工作者,可以使用curl / fopen / soap / etc从工作线程调用它们。

答案 2 :(得分:1)

我认为你应该在循环中的某个时刻尝试调用mysql_free_result() - 来自评论:

  

值得注意的是mysql_query()   仅返回SELECT的资源,   SHOWEXPLAINDESCRIBE次查询。

因此没有结果可以免费提供更新查询。

无论如何,你的方法不是最好的开始。改为尝试使用mysqli paramterized语句,或者(甚至更好)直接更新数据库中的行。看起来循环中的所有SQL都可以使用一个UPDATE语句来处理。

答案 3 :(得分:1)

您可能在每次迭代中看到额外的已用内存的部分原因是PHP尚未(还)垃圾收集不再引用的内容。

答案 4 :(得分:1)

来自php.net memory_get_usage manual

  

参数

     

real_usage将此设置为TRUE以获取   从中分配的实际内存大小   系统。如果没有设置或FALSE只有   记录了emalloc()使用的内存。

将此参数设置为true,脚本显示内存没有增加,就像我预期的那样。

答案 5 :(得分:0)

unset电话无意义/无关紧要。尝试使用mysql_free_result - 它可能会产生一些影响。