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

Posted

技术标签:

【中文标题】2个准备好的语句,2个存储过程,1个mysqli连接【英文标题】:2 prepared statements, 2 stored procedures, 1 mysqli connection 【发布时间】:2015-12-22 18:50:47 【问题描述】:

问题

如何使用准备好的语句(或另一种对 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 连接对于查询before 存储过程很好,但不喜欢任何after 存储过程。

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

不受欢迎的潜在解决方案

A):我可以将它分成两个对后端的调用,但是当数据刷新时,前端的摘要会不同步。

B): 我可以将它们加入一个 MySQL 存储过程,然后在 PHP 中将它们分开,但我也需要将它们分开,所以相同的代码将是有两次。

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

帮助

有什么建议吗?

【问题讨论】:

您的问题的答案一般下一个结果()。要获得特定代码中某些拼写错误的帮助,您必须创建一个完整的、功能齐全的示例并将其发布在此处。根据问题正文中不断编辑的某些草图,无法回答。 阅读php.net entry for next_result() 似乎暗示它与multi_query() 一起使用,而没有提及prepare() 虽然有人建议在comments of the php.net article for prepare() 中使用next_result(),但我尝试使用相同的代码时却没有用。 我把所有的代码都放到tutorialspoint上,可以查看、修改和执行 【参考方案1】:

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

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

我仍然很好奇为什么它不适用于store_result()free_result(),但至少现在有一些工作代码。这是modified code at tutorialspoint.


附带说明,不要使用两个预准备语句和两个存储过程,一种解决方法是使用一个预准备语句来设置变量

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

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

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

【讨论】:

【参考方案2】:

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

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

$mysqli->next_result();

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

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

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

还有另一种更方便的方法:使用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() 方法,这将使您只需一行即可获得结果对象。

【讨论】:

正如问题标题所说,我确实打算使用两个存储过程。我编辑了帖子以试图更好地解释这一点,并解释 next_result() 不适用于准备好的语句。 使用get_result()->fetch_object() 好多了。很好的提示,谢谢!当$stmt->fetch() 已经处于while 循环中时,我不明白在第一条语句之后另一个$stmt->fetch() 的效果。也许我误解了它需要放在哪里。我更新了online code at tutorialspoint。它改用get_result()->fetch_object(),并在第一条语句(行140)的循环之后有一个$stmt->fetch(),但同样的错误仍然存​​在。

以上是关于2个准备好的语句,2个存储过程,1个mysqli连接的主要内容,如果未能解决你的问题,请参考以下文章

PHP 使用 Mysqli 的 prepare 语句有啥好处?

在 mysqli 准备好的语句中使用空值

PHP mysqli为带有out参数的存储过程准备语句

在使用准备好的语句执行插入查询后获取自动增量 ID

我可以重新定义相同的准备好的语句吗?

MySQLi:使用一个准备好的语句插入多行