调用 MySQL 存储过程时出现数据包乱序错误

Posted

技术标签:

【中文标题】调用 MySQL 存储过程时出现数据包乱序错误【英文标题】:Packets out of order error when calling MySQL stored proc 【发布时间】:2013-10-23 18:20:34 【问题描述】:

我正在尝试使用 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。

【问题讨论】:

这可能会有所帮助:bugs.mysql.com/bug.php?id=68359 @Barmar 我已经看到了,并尝试了各种组合,但我不确定如何将 mysqli 发布的解决方案转换为 PDO... 【参考方案1】:

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

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

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

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

【讨论】:

你在哪里设置这个东西? 在您的 PDO 对象上调用 setAttribute 函数。例如:$db = new PDO(...); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);【参考方案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

为防止将来出现问题,任何有相关问题的人请阅读此参考文章Warning: Packets out of order, MySQL server has gone away

【讨论】:

【参考方案3】:

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

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

$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 驱动程序返回两个行集。本机驱动程序要求您在重用连接之前处理两者。你需要advance to the second rowset,即使你不关心里面有什么,也需要close the cursor,然后再准备和调用第二个。像这样:

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

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

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

【讨论】:

对 laravel 有什么想法吗?【参考方案4】:

此 PHP 错误已在 PHP 7.4.15 中修复

错误出现在 mysqlnd 中,当使用准备好的语句时,它无法从内部使用游标的存储过程中正确获取记录。现已修复此问题。

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

【讨论】:

以上是关于调用 MySQL 存储过程时出现数据包乱序错误的主要内容,如果未能解决你的问题,请参考以下文章

协议数据包乱序

MySQL java jdbc MySQLNonTransientConnectionException:得到的数据包乱序

为啥在使用简单存储过程时出现 MySQL 错误 #1312?

MySql存储过程调用错误

错误:数据包乱序。得到:1 预期:0

尝试在存储过程中更新时出现 MySQL 错误代码 1175