2个准备好的语句,2个存储过程,1个mysqli连接


2 prepared statements, 2 stored procedures, 1 mysqli connection

问题

如何使用准备好的语句(或另一种对SQL注入同样安全的查询方法(在同一个mysqli连接中调用两个MySQL存储过程而不会出现以下错误:

Warning: Packets out of order. Expected 1 received 61. Packet size=7 in /...
Warning: mysqli::prepare(): MySQL server has gone away in /...

在tutorialspoint 在线连接了代码

故事

我正在用MySQL数据库做一个PHP后端。我想从一个查询中得到两个结果:一个是每周摘要列表,另一个是所有周的摘要。

┌───────┬────────────┬────────────┬─────
|  Week |    Sales   | Commission | ...
├───────┼────────────┼────────────┼─────
| week1 |  $7,912.12 |    $923.41 | ...
| week2 |  $6,423.48 |    $824.87 | ...
| week3 |  $8,180.67 |    $634.04 | ...
|  ...  |    ...     |    ...     | ...
├───────┼────────────┼────────────┼─────
| total | $67,012.23 |  $7,532.58 | ...
| avg   |  $7,012.54 |    $787.38 | ...
└───────┴────────────┴────────────┴─────

我过去只将每周摘要存储在数据库表中,并使用存储过程来获取所有每周摘要的摘要。在我的PHP代码中,我只是选择了week表中的所有行,然后调用了getWeeksSummary存储过程。

现在我必须能够过滤每周摘要中的数据。我用存储过程getWeeks()替换了一个简单的SELECT ... FROM week来计算所有的每周摘要。

代码

$weeksSummary = new stdClass();
if ($stmt = $mysqli->prepare('CALL getWeeks(?,?,?);')) {
    $stmt->bind_param('sss', $a, $b, $c);
    $stmt->execute();
    $stmt->bind_result($week, $sales, $commission, ...);
    $weeksSummary->weeks = [];
    while($stmt->fetch())
    {
        $week = new stdClass();
        $week->week = $week;
        $week->sales = $sales;
        $week->commission = $commission;
        ...
        $weeksSummary->weeks[] = $week;
    }
    $stmt->free_result();
    $stmt->close();
}
if ($stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?);')) {
    $stmt->bind_param('sss', $a, $b, $c);
    $stmt->execute();
    $stmt->bind_result($avgSales, $totSales, $avgCommission, $totCommission ...);
    $stmt->fetch();
    $weeksSummary->summary = new stdClass();
    $weeksSummary->summary->avgSales = $avgSales;
    $weeksSummary->summary->totSales = $totSales;
    $weeksSummary->summary->avgCommission = $avgCommission;
    $weeksSummary->summary->totCommission = $totCommission;
    ...
    $stmt->free_result();
    $stmt->close();
}
echo json_encode($weeksSummary);

当准备的第一个语句是SELECT week, sales, commission, ... FROM week WHERE a=?, b=?, c=?;而不是CALL getWeeks(?,?,?);时,此代码运行良好。现在我得到这些错误:

Warning: Packets out of order. Expected 1 received 61. Packet size=7 in /...
Warning: mysqli::prepare(): MySQL server has gone away in /...

尝试

1(失败:我为第二个查询使用了一个新的语句对象$stmt2。同样的错误。

2(成功:在第二条语句之前,我关闭了mysqli连接并打开了一个新连接。具有自己准备的语句的第二个mysqli连接运行良好,但连接到数据库的代码是完全独立的,所以这并没有真正的帮助。

3(失败:只是出于好奇,我回到了我最初的工作代码,并重新排序了语句,将存储过程语句放在SELECT语句之前。同样的错误。因此,mysqli连接可以在存储过程之前查询,但不喜欢在存储过程之后查询任何

4(失败:我尝试将$mysqli->next_result();放在第一个语句之后。同样的错误。但是,如果我使用query()而不是prepare()来调用存储过程,那么next_result()确实允许两个存储过程都运行。不过,我希望使用预先准备好的语句,因为它们有助于防止SQL注入。

不受欢迎的潜在解决方案

A(:我可以将其分为两个对后端的调用,但当数据刷新时,前端的摘要将不同步。

B(:我可以将它们连接到一个MySQL存储过程中,然后在PHP中分离它们,但我也需要将它们分离,所以相同的代码会出现两次。

C(:我可以停止使用准备好的语句,但我不知道有任何其他方法可以避免SQL注入。

帮助

有什么建议吗?

好吧,我将尝试回答问题标题,假设在第一条语句中调用的不是常规查询,而是前面提到的两个存储过程之一

调用存储过程后,您总是需要转移到每个存储过程返回的额外空结果集上:

$mysqli->next_result();

此外,在第一次准备好的函数调用后,在获得数据后添加一个额外的fetch((:

$stmt->fetch();
$stmt->free_result();

因为您必须"释放"在服务器端等待的结果集。它可以通过多种方式完成,但最简单的方法是再调用fetch((一次,或者,更严格地说,您必须调用fetch(,直到它返回FALSE,这表明结果集中已经没有更多的行了。在其他代码段中,当在while循环中调用fetch()时,您正在[静默地]执行此操作,但在这里,仅获取一行,您必须显式调用它。

还有另一种更方便的方法:使用get_result()(如果可用(,它将同时解决您的所有问题。现在只需要四行代码:

$stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?)');
$stmt->bind_param('sss', $a, $b, $c);
$stmt->execute();
$weeksSummary = $stmt->get_result()->fetch_object();

get_result()将释放等待的结果集,同时允许您使用fetch_object()方法,该方法将允许您仅在一行中获得结果对象。

阅读mysqli文档,它说$stmt->free_result()是为了释放从$stmt->store_result()分配的内存。由于代码不使用store_result(),因此删除free_result()可以解决错误。

当然,它还规定在查询返回结果集时使用store_result()。我真的不明白为什么(与缓冲有关(,但由于这两个准备好的语句和存储过程在没有store_result()的情况下工作,问题就解决了。

我仍然很好奇为什么它不能与store_result()free_result()一起使用,但至少现在有一些可用的代码。这是tutorialspoint修改后的代码。


附带说明一下,与其使用两个已准备好的语句和两个存储过程,不如使用一个已准备的语句来设置变量

$stmt = $mysqli->prepare('SET @a = ?, @b = ?, @c = ?')
...

然后在查询中使用这些变量来调用存储过程

$result = $mysqli->query('CALL getWeeks(@a,@b,@c)')
...
$result = $mysqli->query('CALL getWeeksSummary(@a,@b,@c)')
...