代码之家  ›  专栏  ›  技术社区  ›  Jasper De Bruijn

PHP脚本内存泄漏

  •  4
  • Jasper De Bruijn  · 技术社区  · 15 年前

    我有一个PHP脚本,它运行一个MySQL查询,然后循环结果,在这个循环中还运行几个查询:

        $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 回复  |  直到 8 年前
        1
  •  4
  •   Jeff    8 年前

    只有当脚本出现“内存耗尽”错误时,才会出现内存泄漏问题。PHP很乐意自己对任何不常用的对象/变量进行垃圾收集,但除非必须这样做,否则收集器将无法正常工作—垃圾收集可能是一个非常昂贵的操作。

    即使您不断地重用相同的对象/变量,内存使用率也会上升,这是正常的——直到内存使用率超过某个级别,收集器才会启动并清理房屋。

    我怀疑,如果将用户ID成批地分为多个组,并发布较少的更新,从而使用每个组更改更多的记录,您可以使事情运行得更快。例如,执行以下操作:

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

    而不是对每个用户进行一次更新。通过DB接口层的往返次数更少,服务器上的时间更多=运行更快。

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

     SELECT @cachednow :p NOW();
     UPDATE .... SET ursUpDate = @cachednow WHERE ....;
    
        2
  •  2
  •   OIS    15 年前

    最好的方法可能是获取所有用户ID并将它们刷新到一个文件中。 然后运行一个新的脚本,它将管道分叉为x个数量的工人无人机。然后给他们一个小的用户ID列表,在他们完成每个列表时进行处理。使用多个CPU/核心/服务器,您可以更快地完成任务。如果一个工人失败了,就重新开始。 要将其他服务器用作工作线程,您可以从工作线程使用curl/fopen/soap/etc来调用它们。

        3
  •  1
  •   Tomalak    15 年前

    我想你应该试着打电话 mysql_free_result() 在循环过程中的某个时刻。 _

    值得注意的是,mysql_query()。 仅返回的资源 SELECT , SHOW , EXPLAIN DESCRIBE 查询。

    因此,没有可供更新查询使用的结果。

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

        4
  •  1
  •   Powerlord    15 年前

    在每次迭代中,您可能会看到额外的已用内存,部分原因是PHP还没有垃圾收集不再被引用的内容。

        5
  •  1
  •   Jasper De Bruijn    15 年前

    php.net memory_get_usage manual :

    参数

    实际使用情况将此设置为true以获取 分配的实际内存大小 系统。如果不设置或不设置,则仅 报告了Emalloc()使用的内存。

    当这个参数设置为true时,脚本显示内存没有增加,正如我预期的那样。

        6
  •  0
  •   troelskn    15 年前

    这个 unset 调用是无意义的/不相关的。尝试用 mysql_free_result 不过,这可能会有一些影响。