【问题标题】:memory leak in php scriptphp脚本中的内存泄漏
【发布时间】:2011-02-05 17:48:20
【问题描述】:

我有一个运行 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 行代码。我发布这部分是因为它看起来是添加最多内存的地方。

【问题讨论】:

  • 我没有看到这里的问题。请发布更多的 while 循环,并指出它正在使用多少内存。
  • 第一次查询前、第一次查询后和循环后返回的 memory_get_usage 是什么?
  • @webbiedave 在循环之前大约 5mb,在循环之后大约 440mb,每个更新查询增加大约 250 个字节。 (其余的内存被添加到循环中的其他地方)。 @Marcus我没有发布更多“其他东西”的原因是因为它大约有300行代码。我发布这部分是因为它看起来是添加最多内存的地方。
  • 我知道这是一个老问题,但下次请包括 PHP 的版本。我在使用 mysqli_fetch_array 时遇到了同样的问题,我确定我使用的是更新版本的 PHP。 (5.5,还是旧的)

标签: php mysql memory-leaks


【解决方案1】:

仅当它以“内存耗尽”错误杀死脚本时,此内存泄漏才会成为问题。 PHP 会很乐意自行对任何未使用的对象/变量进行垃圾收集,但收集器在必须这样做之前不会启动 - 垃圾收集可能是一项非常昂贵的操作。

即使您不断重用相同的对象/变量,内存使用量攀升也是正常的 - 直到内存使用量超过某个水平,收集器才会启动并清理内部空间。

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

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

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

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

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

【讨论】:

  • 感谢您对内存的解释,我试图让这段代码完全不使用内存,但听起来这是不可能的。我更新了我的问题中的更新 qry,因为每个用户都会收到他自己的计算分数,因此无法对查询进行分组。
【解决方案2】:

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

【讨论】:

  • 从长远来看,这可能是更好的解决方案。该脚本是很久以前为相当少量的用户设计的,现在正在针对可能的数百万用户进行测试。
【解决方案3】:

我认为您应该尝试在循环期间的某个时间点调用 mysql_free_result() — 来自 cmets:

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

所以更新查询没有结果可以释放。

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

【讨论】:

  • 循环中发生了很多事情,我只是缩短了查询以使其更易于阅读,并专注于我注意到添加内存的部分。据我了解,我只能在循环之后在 $utmres 上使用 mysql_free_result(),并且不需要在更新查询上使用它。
  • mysql_free_result 对更新语句没有帮助。 “值得注意的是,mysql_query() 只返回 SELECT、SHOW、EXPLAIN 和 DESCRIBE 查询的资源。”
  • @Jasper:当您将循环减少到纯 SQL 活动时,泄漏是否仍然存在?也许泄漏是在发生的其他“事情”中?
  • 我想我应该开始剥离“其他东西”的一部分,看看它是否有帮助。但这对我来说毫无意义,因为我在这段代码之前和之后调用了 memory_get_usage。
  • 另外,使用 unset();在这样的循环中只会暂时释放内存,因为每次迭代都会重新分配变量。您是否验证了您的服务器上是否启用了垃圾收集器(默认情况下)?
【解决方案4】:

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

【讨论】:

  • 好的,这是有用的信息。 PHP 通常什么时候会收集这个?整个循环之后,内存使用量在 440mb 左右,但仍在继续。在页面的后面,在进程的第二部分,页面只是超时而没有错误,我读到这可能是因为没有足够的内存来显示没有足够的内存的错误消息。
  • 这很奇怪,通常,PHP 会因致命的 OOM 错误而崩溃,例如“尝试分配 Y 字节,超出了 X 字节的内存限制”。
  • 特别奇怪,因为这行“ini_set('memory_limit', '4M');”。每 100 个循环我使用一次“flush();usleep(50000);”,这似乎可以防止页面超时。
【解决方案5】:

来自php.net memory_get_usage manual

参数

real_usage 将此设置为 TRUE 以获取 实际分配的内存大小 系统。如果未设置或 FALSE 只有 报告 emalloc() 使用的内存。

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

【讨论】:

    【解决方案6】:

    unset 调用毫无意义/无关紧要。尝试使用mysql_free_result - 它可能会产生一些效果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-16
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2013-01-27
      • 2022-12-17
      • 1970-01-01
      • 2016-02-10
      相关资源
      最近更新 更多