You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP嵌套循环调用存储过程时,第二个查询返回false问题求助

问题:循环内调用存储过程返回false,但循环外正常工作?

我有如下PHP代码:

$user_id = $user["id_user_key"];
$stmt = $db->prepare("CALL spGetUserProducts(?)");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$result = $stmt->get_result();
$data = array();
while($row = $result->fetch_assoc()) {
    $row_array = array();
    $row_array["id"] = $row["id"];
    $row_array["pname"] = $row["pname"];
    $row_array["picon"] = $row["picon"];
    $row_array["menuItems"] = array();
    $product = $row["id"];
    //loop
    $result_opt = $db->query("CALL spGetUserProductViews($user_id, $product)");
    while ($opt_fet = $result_opt->fetch_assoc()) {
        $row_array["menuItems"][] = array(
            "id" => $opt_fet["id"],
            "vname" => $opt_fet["vname"],
            "isheader" => $opt_fet["isheader"]
        );
    }
    array_push($data, $row_array);
}
$stmt->close();
echo json_encode($data);

第一个循环可正常获取$db连接,第一条预处理语句执行后能得到结果,但第二个查询语句返回false。不过将该语句放在循环外执行时却能正常工作,请问这是什么原因?


原因分析与解决方案

嗨,这个问题我之前排查过类似的情况,核心原因是MySQL对存储过程调用的结果集处理规则,再加上你代码里还藏着个SQL注入的坑,咱们一步步拆解:

1. 未关闭的结果集导致连接阻塞

当你调用第一个存储过程spGetUserProducts拿到$result后,在while循环遍历每一行的过程中,这个结果集其实并没有被完全释放。MySQL有个严格的规则:同一个数据库连接上,如果存在未处理完毕的结果集(包括存储过程返回的所有结果),你根本没法执行新的查询,哪怕是另一个存储过程调用也不行。

你把第二个查询放到循环外时,第一个结果集已经被完全遍历完,连接自动释放了资源,所以新查询能正常跑起来。

2. 额外的隐患:SQL注入

你现在第二个查询是直接把$user_id和$product拼进SQL字符串的:

$result_opt = $db->query("CALL spGetUserProductViews($user_id, $product)");

这是典型的SQL注入风险!哪怕这俩变量看起来是数字类型,也不能直接拼接,必须用预处理语句,不然哪天被恶意构造的数据钻空子就麻烦了。


修复步骤

第一步:先清掉第一个存储过程的所有结果集

在循环里执行第二个查询前,必须确保第一个语句的所有结果集都被处理完。存储过程有时候会返回多个结果集(哪怕你只用到一个),所以得调用$stmt->next_result()把多余的结果都清掉,释放连接资源。

第二步:用预处理语句调用第二个存储过程

把直接拼接SQL的写法改成预处理绑定参数的方式,既安全又符合MySQL的连接规范。

修改后的完整代码如下:

$user_id = $user["id_user_key"];
$stmt = $db->prepare("CALL spGetUserProducts(?)");
$stmt->bind_param('i', $user_id);
$stmt->execute();
$result = $stmt->get_result();
$data = array();

while($row = $result->fetch_assoc()) {
    $row_array = array();
    $row_array["id"] = $row["id"];
    $row_array["pname"] = $row["pname"];
    $row_array["picon"] = $row["picon"];
    $row_array["menuItems"] = array();
    $product = $row["id"];

    // 关键操作:清理第一个存储过程的所有剩余结果集
    while ($stmt->next_result()) {
        if ($res = $stmt->get_result()) {
            $res->free(); // 释放结果集资源
        }
    }

    // 改用预处理调用第二个存储过程,避免注入
    $stmt_opt = $db->prepare("CALL spGetUserProductViews(?, ?)");
    $stmt_opt->bind_param('ii', $user_id, $product); // 两个int类型参数
    $stmt_opt->execute();
    $result_opt = $stmt_opt->get_result();

    while ($opt_fet = $result_opt->fetch_assoc()) {
        $row_array["menuItems"][] = array(
            "id" => $opt_fet["id"],
            "vname" => $opt_fet["vname"],
            "isheader" => $opt_fet["isheader"]
        );
    }

    // 及时关闭第二个语句的资源
    $result_opt->free();
    $stmt_opt->close();

    array_push($data, $row_array);
}

// 清理第一个语句的资源
$result->free();
$stmt->close();
echo json_encode($data);

为什么这样能解决问题?

  • $stmt->next_result()会遍历第一个存储过程返回的所有结果集,确保连接上没有未处理的残留,这样新的查询就能顺利执行。
  • 预处理语句彻底杜绝了SQL注入风险,同时也遵循了MySQL的连接使用规范。

另外,还有个性能优化的小建议:可以考虑把两个存储过程的逻辑合并成一个,一次性查出所有产品和对应的视图数据,这样就不用在循环里反复请求数据库了,性能会提升不少哦。


内容的提问来源于stack exchange,提问作者dexter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:54:34