【问题标题】:PDO Memory ExhaustedPDO 内存耗尽
【发布时间】:2015-08-23 18:39:00
【问题描述】:

这是一个常见问题,但我没有选择像这样编码它只是为了在 Excel 文件中获得适当的标题和正文

这里是如何开始的

当请求打印时,我首先开始查询以获取数据库中的标题

SELECT instruments.in_id, instrument_parameters.ip_id,
CASE WHEN gv_x_ipid = -1 THEN 'datetime' ELSE '' END xlabel,
CASE WHEN ip_label LIKE '%Reservoir%' THEN 0 ELSE in_order END legendIndex,
CASE WHEN in_name = 'General' THEN ip_label ELSE in_name END ylabel            
FROM graph_plot
LEFT JOIN attributes gptype ON gp_type = gptype.at_id
LEFT JOIN graph_value ON gp_id = gv_gpid
LEFT JOIN instrument_parameters ON gv_y_ipid = ip_id
LEFT JOIN attributes pmunit ON ip_unit = pmunit.at_id
LEFT JOIN instrument_reading yvalue ON gv_y_ipid = iv_ipid
LEFT JOIN instruments ON iv_inid = in_id
WHERE gp_diid = :di_id AND 
      gp_type = :rpt_type AND 
      iv_status = 'Y' AND
      iv_inid in (".implode(",", $coll->inid).") AND
      gv_y_ipid in (".implode(",", $coll->ipid).")
GROUP BY ylabel
ORDER BY legendIndex

这将产生许多标题,我将使其成为这样

DATE | Instrument1 | Instrument2 | Instrument3

Instrument? 将根据上面的查询是动态的。我将其存储在新变量中。但是保存数据库结果的原始变量保持不变。

稍后,使用相同的参数:di_id:rpt_type,以及另一个附加参数startDtendDt 进行另一个查询,以返回数据库中可用日期的长列表。这是基于startDtendDt

$sql2 = "SELECT iv_reading FROM instrument_reading WHERE iv_inid = :inid AND iv_ipid = :ipid AND iv_date = :dt AND iv_status = 'Y'";    

当它完成获取日期时,我会像这样进行两个循环

foreach ($dates as $key => $dt) {       
    foreach ($resp as $InstNo => $InstRow) {
        try {
            $stmt2->execute(array(':dt' => $dt, ':inid' => $InstRow->in_id, ':ipid' => $InstRow->ip_id));
            $rowDb = $stmt2->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT);
        } catch(PDOException $e) {
            echo '{"error":{"text":"'. $e->getMessage() .'"}}'; 
        }
    }
}

首先,它开始循环日期,然后开始循环标题(基于在获取日期之前进行的查询)。我的问题一直卡在这里

$stmt2->execute(array(':dt' => $dt, ':inid' => $InstRow->in_id, ':ipid' => $InstRow->ip_id));

你怎么看?有没有更好的处理方法?

为了您的信息,我使用 Slim 和 PHPExcel。 PHPExcel 可能有内存问题,我正在考虑切换到Spout,但文档仍然是关于基本内容的。

【问题讨论】:

  • 请把表名(或别名)放在SELECT中每一列的前面;我们看不到查询是如何工作的。
  • 为什么要嵌套foreach?不能在SELECT中循环,你只需取回一组行。
  • 您希望每个 execute 有多少行?
  • @MuhaiminAbdul 尝试设置ini_set('memory_limit', '-1'); 记住这不好,但为了测试,将它设置为 -1 并查看
  • 您的问题不清楚您收到的错误消息是什么。当你说“我总是卡在这里”时,你的意思是它在这条线上抛出异常吗?如果是这样,什么类型以及消息是什么?您是否尝试过在其他上下文中运行查询,例如 PHPMyAdmin 或 MySQL 控制台?

标签: php mysql pdo mariadb


【解决方案1】:

在您的 SQL 中,您可以考虑使用limit clause 来减轻内存负载,如下所示:

$handle = fopen("file.csv", "wb");
$statement = "
SELECT  instruments.in_id, instrument_parameters.ip_id,
       CASE WHEN gv_x_ipid = -1 THEN 'datetime' ELSE '' END xlabel,
       CASE WHEN ip_label LIKE '%Reservoir%' THEN 0 ELSE in_order END legendIndex,
       CASE WHEN in_name = 'General' THEN ip_label ELSE in_name END ylabel
    FROM  graph_plot
    LEFT JOIN  attributes gptype ON gp_type = gptype.at_id
    LEFT JOIN  graph_value ON gp_id = gv_gpid
    LEFT JOIN  instrument_parameters ON gv_y_ipid = ip_id
    LEFT JOIN  attributes pmunit ON ip_unit = pmunit.at_id
    LEFT JOIN  instrument_reading yvalue ON gv_y_ipid = iv_ipid
    LEFT JOIN  instruments ON iv_inid = in_id
    WHERE  gp_diid = :di_id
      AND  gp_type = :rpt_type
      AND  iv_status = 'Y'
      AND  iv_inid in (".implode(",", $coll->inid).")
      AND  gv_y_ipid in (".implode(",", $coll->ipid).")
    GROUP BY  ylabel
    ORDER BY  legendIndex
    LIMIT  250
";
$prep = $dbh->prepare($statement);
for ($i = 0; $prep -> rowCount < 250; $i+= 250) {
    fputcsv(prep->fetchAll());
    $prep = $dbh->prepare($statement.' OFFSET'.$i);
}
fclose($handle);

或者,您可以使用system 并调用SELECT INTO,设置权限(如有必要),并且 Bob 是您的叔叔。

【讨论】:

  • 我无法设置限制,因为我会将其全部转储到 Excel 文件中
  • 当然可以...在循环外打开文件,然后每 250 行转储一次,直到完成。
  • 如何移动到 next 250 行输入?也许您需要OFFSET?但是你必须希望表没有被添加到块之间或从块之间删除。
  • 谢谢,@RickJames... 为确保表格保留,将选择语句包装在事务中
  • 我不确定一笔交易是否足够。此外,ORDER BY legendIndex 似乎是非确定性的——它似乎很多时候都是 0,否则 in_order,在所有 JOINs 之后可能并不明显。
【解决方案2】:

您尚未终止 fetch 循环。

$rowDb = $stmt2->fetch(PDO::FETCH_NUM, PDO::FETCH_ORI_NEXT);

获取“下一个”行或关闭“光标”并终止。

您是否希望得到一排?如果是这样,请考虑执行 fetchAll。 (注意:结果集可能是数组中的一个额外级别。)

【讨论】:

  • 你的意思是我没有终止提取?我不明白你。
【解决方案3】:

PDO MySQL 驱动程序会做一些缓冲,这会在循环大型数据集时导致内存耗尽。您可以使用 $pdo-&gt;setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); 将其关闭,这应该可以解决问题。

$pdo = new PDO('mysql:localhost', $username, $password);
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

$stmt = $pdo->prepare('SELECT * FROM instrument...');
$stmt->execute($parameters);

while($row = $stmt->fetch()) {
    // Insert logic to write the row to the destination
}

如果您只想为该查询设置属性,您也可以这样做:

$stmt = $pdo->prepare('SELECT * FROM instrument...', [
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
]);

请记住,在完成无缓冲查询之前,您将无法运行其他查询。如果您不需要剩余的结果,您可以使用 $stmt-&gt;closeCursor() 提前关闭旧光标。我也无法谈论它的性能,但它在编写一次性脚本时解决了我的问题。

MySQL 的文档中简要提到了该设置: https://dev.mysql.com/doc/connectors/en/apis-php-pdo-mysql.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-10
    • 2012-08-19
    • 2011-02-09
    • 2015-10-01
    • 2018-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多