8

我正在尝试使用 PDO 调用存储过程,但在尝试获取结果时出现以下错误。

警告:数据包乱序。预计收到 1 个 16。数据包大小=163

我的存储过程正在使用我在从临时表中选择之前关闭的两个游标。我怀疑这可能是问题所在,因为我可以直接在 MySQL 中调用我的 SP 并且可以看到结果。在迁移到 php_pdo_mysql.dll 之前使用 php_mysql 扩展时,我也从未遇到过这个 SP 的问题。我还可以使用 PDO 在 PHP 中调用包含 INPUT 参数的其他更简单的存储过程,并且可以在没有任何错误的情况下获取结果。

这是返回错误的代码:

$db = new PDO('mysql:host='.__DB_HOST__.';dbname='.__DB_NAME__.';charset=utf8', __DB_USER__, __DB_PASS__);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

/* DOES NOT WORK */
$queryResult = $db->prepare("CALL GetResults(:siteId,null)");
$siteId = 19;
$queryResult->bindValue(':siteId', $siteId, PDO::PARAM_INT);
$queryResult->execute();
$result = $queryResult->fetchAll(PDO::FETCH_ASSOC); // returns packets out of order warning
print_r($result);

我在 Try/Catch 块中有这段代码,没有抛出异常。事实上,PHP 在浏览器中将其显示为警告。

我的存储过程签名如下所示:

CREATE DEFINER=`root`@`localhost` 
PROCEDURE `GetResults`(IN siteIdParam INT(11), IN siteSearchText VARCHAR(45))

我也不确定问题是否在于将null作为参数之一传递。有时第一个参数传递null,有时是第二个。但无论如何,它总是直接在 MySQL 服务器上工作。

我尝试了 bindParam 和 bindValue,结果相同。我也可以发布我的 SP,但这可能有点矫枉过正。

有没有办法从 PDO 扩展打开额外的日志记录?

有什么想法或建议吗?如果您需要更多信息,请告诉我。

注意:我使用的是 PHP v5.5.4 和 MySQL v5.6.14。

4

4 回答 4

15

在花了很多时间尝试隔离我的部分代码来解决这个问题之后,我注意到在将 ATTR_EMULATE_PREPARES 标志设置为 true 后错误就消失了。

$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

这告诉 PDO 模拟准备好的语句,而不是 MySQL 本身。从我一直在阅读的内容来看,如果您使用的是最新版本的 MySQL 和 PHP,通常建议关闭此标志(默认情况下为 true)。您可以在这篇SO 文章中找到更多信息。

我确实认为这是 MySQL 的一个错误(我在 5.6.17 版之前遇到了这个问题)。关于这个特定问题的讨论不多,因此希望这可以节省其他人数小时的故障排除时间。这个问题也在这个 MySQL bug page上讨论过,但发布的解决方案在我的情况下并没有帮助我。

于 2014-05-19T15:31:35.153 回答
3

我一尝试更改 PDO::ATTR_EMULATE_PREPARES 属性就遇到了同样的问题。

当我尝试在同一个 PDO 连接上调用第二个准备好的语句时,我可以可靠地重现您所描述的内容。像这样的东西:

$db = new PDO('mysql:host='.__DB_HOST__.';dbname='.__DB_NAME__.';charset=utf8', __DB_USER__, __DB_PASS__);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

/* DOES NOT WORK */
$queryResult = $db->prepare("CALL GetResults(:siteId,null)");
$siteId = 19;
$queryResult->bindValue(':siteId', $siteId, PDO::PARAM_INT);
$queryResult->execute();
$result = $queryResult->fetchAll(PDO::FETCH_ASSOC); // works fine
print_r($result); 

// Call a second stored procedure
$secondCall = $db->prepare("CALL GetOtherResults()");
$secondCall->execute();
$secondResult = $secondCall->fetchAll(PDO::FETCH_ASSOC); // returns packets out of order
print_r($secondResult);

我知道您的示例仅显示一个调用,但要触发此错误,存储的 proc 调用不必位于同一个文件中,只需共享相同的 PDO 连接。我希望我下面的建议仍然有帮助。

当您执行存储过程时,MySQL 驱动程序返回两个行集。本机驱动程序要求您在重用连接之前处理两者。您需要前进到第二个 rowset,即使您不关心其中的内容,并且还要在准备和调用第二个之前关闭游标。像这样:

...
$queryResult->execute();
$result = $queryResult->fetchAll(PDO::FETCH_ASSOC);
$queryResult->nextRowSet();
$queryResult->closeCursor();

// Now you can prepare your second statement
...

如果您忘记进行这两个额外的调用,看起来 PDO 的模拟准备会更加宽容。

于 2015-05-16T11:21:01.153 回答
2

@http203 我在使用 WAMP 服务器和 mysql 5.6.17 时遇到了同样的问题

$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

解决了这个问题,但我也建议增加 mysql.ini 中的查询限制

max_allowed_packet = 1MB //Default

max_allowed_packet = 3MB

为了防止将来出现问题,对于有相关问题的任何人,请阅读此参考文章警告:数据包乱序,MySQL 服务器已消失

于 2014-08-19T18:12:13.930 回答
1

此 PHP 错误已在 PHP 7.4.15 中修复

该错误出现在 mysqlnd 中,当使用准备好的语句时,它无法从内部使用游标的存储过程中正确获取记录。这已经被修补了。

对于使用旧版本的人,切换到模拟准备的解决方法仍然有效。

于 2021-08-03T16:03:47.803 回答