为什么PHP7中该MySQL查询需要调用两次PDO::prepare,PHP8却不需要?
问题根因
- 你遇到的是PHP7.2配套的mysqlnd 5.0.12版本驱动的已知兼容性问题:你在
MYSQL_ATTR_INIT_COMMAND参数中传入了多条SQL命令(SET NAMES和SET time_zone),执行后产生了多组残留结果集,旧版本驱动不会自动消费这些结果集,导致第一次调用prepare()生成的语句对象执行时直接静默失败,无法返回查询结果。 - 重复调用
prepare()能恢复正常的原因:你把第一次生成的PDOStatement对象覆盖了,触发了旧对象的析构逻辑,析构过程会自动清空连接上残留的未读结果集,因此第二次生成的语句对象可以正常执行。 - PHP8环境无此问题的原因:8.0配套的高版本mysqlnd驱动已经修复了该问题,连接初始化命令产生的结果集会被自动消费,不会残留影响后续操作。
- 无有效报错的原因:残留结果集导致PDO的错误状态记录异常,连接层面的
errorInfo()只会保留上一次成功操作的状态,执行失败的语句对象也没有正确写入错误信息,因此返回空的00000错误码。
可行解决方案
方案1:连接建立后主动清空残留结果集(最直接,改动最小)
创建PDO实例后立刻循环读取所有残留的结果集,再进行后续操作:
$this->conn = new PDO('mysql:host=' . DB_SERVER . ';dbname=' . DB_NAME . ';charset=utf8', DB_USER, DB_PASS, [PDO::MYSQL_ATTR_INIT_COMMAND =>"SET NAMES utf8;SET time_zone = '" . TIMEZONE . "'"]); // 新增以下代码清空所有残留结果集 while ($this->conn->nextRowset()) {} // 再设置报错模式等属性 $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
方案2:拆分初始化命令,避免多条语句放在INIT_COMMAND中
不要在MYSQL_ATTR_INIT_COMMAND参数中传入多条SQL,连接建立后手动执行初始化命令:
// 移除MYSQL_ATTR_INIT_COMMAND参数 $this->conn = new PDO('mysql:host=' . DB_SERVER . ';dbname=' . DB_NAME . ';charset=utf8', DB_USER, DB_PASS); // 手动执行初始化命令 $this->conn->exec("SET NAMES utf8;SET time_zone = '" . TIMEZONE . "'"); // 可加清空逻辑兜底 while ($this->conn->nextRowset()) {} $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
内容的提问来源于stack exchange,提问作者Cyrille
相关产品推荐
相关产品推荐

