【问题标题】:PHP memory limit with SQL result带有 SQL 结果的 PHP 内存限制
【发布时间】:2019-01-03 12:58:32
【问题描述】:

我正面临一个根本不应该解决的问题,但不知何故我不能。这是场景:

我想从 SQL 语句中获取结果到 PHP 数组中,看起来很简单吧?

问题是它给了我关于 memory_limit 的错误(致命错误:内存不足(已分配 408944640)(试图分配 805306376 字节))。

这是我的代码,它使用的语句结果较少(我需要使用的语句包含 +/- 4000 条记录)。

$connect = @new mysqli($host, $db_user, $db_password, $db_name);

if($connect->connect_errno!=0)
{
    echo "Error: ".$connect->connect_errno."Why: ". $connect->connect_error;
}
else{
    $query = "SELECT meta_value, post_date 
FROM 
  wp_postmeta
  INNER JOIN 
  wp_posts
  ON wp_posts.ID=wp_postmeta.post_id 
where wp_posts.post_type ='shop_order' AND wp_postmeta.meta_key = '_order_total'";
    $sql_query=mysqli_query($connect,$query);
    if ($sql_query->num_rows > 0){
    while(($row = mysqli_fetch_assoc($sql_query))!== false){
        $result[] = $row;
    }} else {
        echo "0 results";
    }
}

echo json_encode($result);

不太确定为什么它使用 0.8 GB 内存(对于一个简单的语句来说似乎已经足够了。但是为了尝试一下,我已将 php.ini 文件中的 memory_limit 更改为:memory_limit=8192M。但它根本没有帮助(检查 memory_limit 是否应用了调用 php_info() 函数)。

谢谢你们!

【问题讨论】:

  • 不是语句,是数组$result[]中存储的表中的数据
  • 你真的需要INNER JOIN吗?
  • 你的 mysqli_fetch_assoc 已经是一个数组了。那么为什么不回显它并跳过while循环
  • 你真的同时需要这 4000 行吗?
  • 你真正想要达到的目标是什么?这可能是XY problem

标签: php sql


【解决方案1】:

不要在内存中保存完整的结果表,否则您的服务器很容易受到 DOS 攻击。服务器必须能够同时回复许多请求。一次只处理一行并释放分配的内存。

您应该直接输出以逗号分隔的 JSON 块。

echo '[';

$sep = '';
while(($row = mysqli_fetch_assoc($sql_query))!== false){
    echo $sep . json_encode($row);
    $sep = ',';
}

echo ']';

或者,您可以将其缓存在临时文件中。

在较新的 MySql 版本上,您可以尝试:

SELECT JSON_OBJECT('meta_value', meta_value, 'post_date', post_date)
FROM wp_postmeta 
INNER JOIN wp_posts ON wp_posts.ID=wp_postmeta.post_id 
WHERE wp_posts.post_type ='shop_order' 
AND wp_postmeta.meta_key = '_order_total' 
INTO OUTFILE '/tmp/mysql-temp-GENERATED_UUID';

这会生成一个带有 JSON 行的文件,但不是逗号分隔的。 正如mysqlserverteam.com 上所述,聚合函数JSON_ARRAYAGG 应该可以解决这个问题。 (不幸的是,我的安装存在第三方存储库冲突,因此我无法对其进行测试。)

SELECT JSON_ARRAGG(JSON_OBJECT('meta_value', meta_value, 'post_date', post_date))
FROM wp_postmeta 
INNER JOIN wp_posts ON wp_posts.ID=wp_postmeta.post_id 
WHERE wp_posts.post_type ='shop_order' 
AND wp_postmeta.meta_key = '_order_total' 
INTO OUTFILE '/tmp/mysql-temp-GENERATED_UUID';

如果 JSON_ARRAYAGG 由于某种原因导致问题,解决方法(至少在类 unix 系统上)是:

<?php
  $guid = bin2hex(openssl_random_pseudo_bytes(16));
  $tmp_filename = "/tmp/mysql-json-{$guid}.json";
  /* let MySql generate the tempfile, then */

  passthru('sed \'1 s/^/[/; $! s/$/,/ ; $ s/$/]/\' ' . $tmp_filename);
?>

【讨论】:

  • 结果是[{},{}{}]
  • 是的,已编辑。应该在动态编码之前睡觉。 :)
  • 不,$sep 是第一次迭代后设置的前缀
  • 它正在显示结果,但是加载所有结果的速度很慢。在将数据保存到数组时考虑是否不是相同的情况。也许只有一种更好的方法可以将这么多“记录”传递给 JavaScript。大家有什么想法吗?
  • @Sinner 取决于您的 MySql 版本。 SELECT JSON_ARRAY(...) / SELECT JSON_OBJECT(...) INTO OUTFILE '/tmp/[some-random-uid].json' dev.mysql.com/doc/refman/8.0/en/json-creation-functions.html / dev.mysql.com/doc/refman/8.0/en/select-into.html 完成后不要忘记删除临时文件。
【解决方案2】:

为什么不用Wordpress这样查询和分页:

$args = array(
    "post_type" => "shop_order",
    "posts_per_page" => 1000,
    "post_status" => array("any"),
    "paged" => $paged, // pass your pagination here from javascript
    "meta_query" => array(
        "key" => "_order_total",
        "compare" => "EXISTS"
    )
);
$custom_posts = get_posts($args);

echo json_encode($custom_posts);

【讨论】:

  • 用户需要结果来创建图表。对结果进行分页并不是他所需要的。
  • 我只需要一个简单的应用程序来分析数据并在图表上显示它们,所以我认为使用 wordpress 没有意义。
  • 当然可以。他是OOM。分页将允许他以块的形式执行他的查询而不会耗尽内存。
  • 然后我会使用 ajax 或 API 进行查询并填充您的图表。这将允许您处理无限量的记录而不会耗尽内存。
  • @TimHallman 这可能是个好主意,我可能会尝试,但必须阅读有关 AJAX 和 API 解决方案的更多信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 2012-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多