【问题标题】:php, mysql, my memory leakingphp,mysql,我的内存泄漏
【发布时间】:2011-11-05 07:04:42
【问题描述】:

我没想到这个脚本(丢弃)会泄漏,我还没有弄清楚罪魁祸首是什么。你能发现什么吗?尽管这是一次性代码,但我担心我将来会重复此操作。我从来不需要在 PHP 中管理内存,但是由于数据库中的行数,它正在炸毁我的 php 实例(已经将内存增加到 1Gb)。

california 表比其他表特别大(当前为 220 万行,因为我删除了重复行而减少了)。第 31 行出现内存错误 ($row = mysql_fetch_assoc($res))

致命错误:允许的内存大小为 1073741824 字节已用尽(已尝试 在 C:\Documents and Settings\R\My Documents\My 中分配 24 个字节) 第31行的网页\cdiac\cdiac_dup.php

PHP 5.3.0,mysql 5.1.36。 wamp 安装的一部分。

这是完整的代码。这个脚本的目的是删除重复的条目(数据被获取到分段表中,这在当时要快得多,但现在我必须合并这些表。)

是什么原因造成的?我忽略了什么?还是我只需要观察内存大小并在它变大时手动调用垃圾收集?

<?php

define('DBSERVER', 'localhost');
define('DBNAME', '---');
define('DBUSERNAME', '---');
define('DBPASSWORD', '---');

$dblink = mysql_connect(DBSERVER, DBUSERNAME, DBPASSWORD);
mysql_select_db(DBNAME, $dblink);


$state = "AL";
//if (isset($_GET['state'])) $state=mysql_real_escape_string($_GET['state']); 
if (isset($argv[1]) ) $state = $argv[1];

echo "Scanning $state\n\n";


// interate through listing of a state to check for duplicate entries (same station_id, year, month, day)
$DBTABLE = "cdiac_data_". $state;
$query = "select * from $DBTABLE ";
$query .= " order by station_id, year, month, day ";

$res = mysql_query($query) or die ("could not run query '$query': " . mysql_errno() . " " . mysql_error());

$last = "";
$prev_row;
$i = 1;
$counter = 0;
echo ".\n";
while ($row = mysql_fetch_assoc($res)) {  
  $current = $row["station_id"] . "_" . $row["year"] . "_" . sprintf("%02d",$row["month"]) . "_" . sprintf("%02d",$row["day"]);
  echo str_repeat(chr(8), 80) . "$i  $current ";
  if ($last == $current) {
    //echo implode(', ', $row) . "\n";

    // merge $row and $prev_row
    // data_id  station_id, state_abbrev, year, month,  day,  TMIN, TMIN_flags, TMAX, TMAX_flags, PRCP, PRCP_flags, SNOW, SNOW_flags, SNWD, SNWD_flags

    printf("%-13s %8s %8s\n", "data_id:", $prev_row["data_id"], $row["data_id"]);
    if ($prev_row["data_id"] == $row["data_id"]) echo " + ";

    $set = "";
    if (!$prev_row["TMIN"] && $row["TMIN"])  $set .= "TMIN = " . $row["TMIN"] . ", ";
    if (!$prev_row["TMIN_flags"] && $row["TMIN_flags"])   $set .= "TMIN_flags = '" . $row["TMIN_flags"] . "', ";
    if (!$prev_row["TMAX"] && $row["TMAX"])   $set .= "TMAX = " . $row["TMAX"] . ", ";
    if (!$prev_row["TMAX_flags"] && $row["TMAX_flags"])   $set .= "TMAX_flags = '" . $row["TMAX_flags"] . "', ";
    if (!$prev_row["PRCP"] && $row["PRCP"])   $set .= "PRCP = " . $row["PRCP"] . ", ";
    if (!$prev_row["PRCP_flags"] && $row["PRCP_flags"])   $set .= "PRCP_flags = '" . $row["PRCP_flags"] . "', ";
    if (!$prev_row["SNOW"] && $row["SNOW"])   $set .= "SNOW = " . $row["SNOW"] . ", ";
    if (!$prev_row["SNOW_flags"] && $row["SNOW_flags"])   $set .= "SNOW_flags = '" . $row["SNOW_flags"] . "', ";
    if (!$prev_row["SNWD"] && $row["SNWD"])   $set .= "SNWD = " . $row["SNWD"] . ", ";
    if (!$prev_row["SNWD_flags"] && $row["SNWD_flags"])   $set .= "SNWD_flags = '" . $row["SNWD_flags"] . "', ";

    $delete = "";
    $update = "";
    if ($set = substr_replace( $set, "", -2 )) $update = "UPDATE $DBTABLE SET $set WHERE data_id=".$prev_row["data_id"]." and year=".$row["year"]." and month=".$row["month"]." and day=".$row["day"].";\n";
    if ($row["data_id"] != $prev_row["data_id"]) $delete = "delete from $DBTABLE where data_id=".$row["data_id"]." and year=".$row["year"]." and month=".$row["month"]." and day=".$row["day"].";\n\n";

    if ($update) {
      $r = mysql_query($update) or die ("could not run query '$update' \n".mysql_error());
    }
    if ($delete) {
      $r = mysql_query($delete) or die ("could not run query '$delete' \n".mysql_error());
    }    

    //if ($counter++ > 5) exit(0);
  }
  else {
    $last = $current;
    unset($prev_row);
    //copy $row to $prev_row
    foreach ($row as $key => $val) $prev_row[$key] = $val;
  }

  $i++;
}

    echo "\n\nDONE\n"; 
?>

【问题讨论】:

  • 虽然在我删除内容时它会变小,但我的加利福尼亚表有 2,200,000 多行。我用完了大约 170 万行的内存(设置为 1Gb)。
  • 您应该使用现代 mysql 扩展之一 MySQLiPDO_MYSQL
  • @kingcruch,我相信你是对的。您是否有任何偏好或明确的理由来使用其中一种?
  • @fbas:mysqli 级别较低,PDO 级别较高。 mysqli 允许更多地访问 MySQL 特定的功能。 PDO 有更好的接口,支持其他 DB。见问题"mysqli or PDO - what are the pros and cons?"

标签: php mysql memory-leaks


【解决方案1】:

我在尝试追踪我的脚本上的内存使用问题时发现了这一点。为我解决了这个问题后,我认为值得在这里为下一个遇到相同问题的人添加回复。

我使用的是 mysqli,但同样适用于 mysql。

我发现的问题是查询没有释放他们的结果。解决方案是在执行更新和删除查询后使用 mysqli_free_result()。但更重要的是,在循环的 mysqli_query 上,我使用了 *MYSQLI_USE_RESULT* 的额外参数。这会产生副作用,因此请为更新和删除查询使用单独的连接。

【讨论】:

    【解决方案2】:

    更聪明地工作,而不是更努力地工作:

    SELECT station_id, year, month FROM table
        GROUP BY station_id, year, month
        HAVING COUNT(*) > 1
    

    这将为您提供不止一次出现在表中的所有 station_id/year/month 元组。假设您的大部分数据不重复,这将为您节省大量内存,因为现在您只需检查这些元组并修复与它们匹配的行。

    【讨论】:

    • 我尝试了类似的方法。拿了一辈子。我取消了。
    • 即使有适当的索引?
    • 对不起,没有意识到我的回答听起来如此不屑一顾。您的解决方案有效。并且不会因为结果集很小而占用内存。我仍然想知道是否有办法释放循环中的内存,但您的查询很好。
    • 如果您使用的是 PHP 5.2,请尝试升级到 5.3。它有更好的内存管理。
    • 它是 5.3.0,我认为这只是拥有大量结果集的问题。它不会释放它的内存。我想解决方案是对较大的集合进行较小的 LIMIT 查询。如果我正在编辑行以使其在后续查询中重新排序,这可能是一个问题。
    【解决方案3】:

    我会尝试两件事:

    1) 不要使用mysql_query 在循环内运行 UPDATE 和 DELETE 查询,而是将它们保存在文本文件中,以便稍后执行。例如:file_put_contents('queries.sql', $update, FILE_APPEND );

    2) 不要在while ($row = mysql_fetch_assoc($res)) 循环中执行所有操作,而是首先获取所有 SELECT 查询结果,然后关闭数据库连接以释放所有数据库资源,包括查询结果。只有在此之后,执行循环过程。

    如果在将数据库结果存储在一个数组中时内存不足,您可以尝试将结果保存在临时文件中(每行一条记录/FILE_APPEND),然后在循环中使用此文件(读取一行每条记录,使用fgets 函数)。

    【讨论】:

    • 我同意 J. Bruni 的观点,加上最后一个“else”块真的很香。你为什么不直接做$prev_row = $row 而不是那个foreach?我知道这不是内存泄漏的原因。
    • @sebastian,我最初使用 $prev_row = $row,但是当我遇到内存错误时,这是​​我改变的第一件事,想知道这是否是泄漏的原因 - 所以我做了一个行的副本而不是参考。它没有效果。
    猜你喜欢
    • 2023-03-17
    • 1970-01-01
    • 1970-01-01
    • 2016-02-10
    • 2012-04-17
    • 2012-01-12
    • 2017-01-28
    • 2016-03-30
    • 1970-01-01
    相关资源
    最近更新 更多