如何使用预准备语句(或另一种对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
存储过程。
现在我必须能够过滤每周摘要中的数据。我用存储过程SELECT ... FROM week
替换了一个简单的getWeeks()
来计算所有每周摘要。
$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
语句之前的语句。同样的错误。因此{<1}}连接可以在存储过程之前查询,但在存储过程之后不喜欢任何。
4)失败: 我尝试在第一个语句后添加mysqli
。同样的错误。但是,如果我使用$mysqli->next_result();
而不是query()
来调用存储过程,prepare()
确实允许两个存储过程运行。我想使用预备语句,因为它们有助于防止SQL注入。
A): 我可以将它分成两个后端调用,但数据刷新时前端的摘要将不同步。
B): 我可以将它们加入到一个MySQL存储过程中,然后在PHP中将它们分开,但我也需要将它们分开,所以相同的代码将是有两次。
C): 我可以停止使用预准备语句,但我不知道有任何其他方法可以避免SQL注入。
有什么建议吗?
答案 0 :(得分:5)
好吧,我会尝试回答问题标题,假设在第一个语句中不是常规查询,而是上述两个存储过程之一调用。
调用存储过程后,您始终必须移动每个存储过程返回的其他空结果集:
$mysqli->next_result();
此外,在第一次准备好函数调用之后,在获取数据后添加一个额外的fetch():
$stmt->fetch();
$stmt->free_result();
因为你必须&#34;免费&#34;结果集在服务器端等待。它可以通过多种方式完成,但最简单的方法是再次调用fetch(),或者更严格地说,你必须调用fetch()直到它返回FALSE,表明结果集中没有剩下的行。当你在fetch()
循环中调用while
时,你正在[静默]在其他片段中这样做,但是在这里只获取一行,你必须明确地调用它。
还有另一种方式,更方便:使用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()
方法,这样您就可以在一行中获取结果对象。
答案 1 :(得分:3)
阅读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)')
...