MySQL 错误 2014 的原因在其他无缓冲查询处于活动状态时无法执行查询
Posted
技术标签:
【中文标题】MySQL 错误 2014 的原因在其他无缓冲查询处于活动状态时无法执行查询【英文标题】:Causes of MySQL error 2014 Cannot execute queries while other unbuffered queries are active 【发布时间】:2013-06-30 07:45:28 【问题描述】:我的服务器运行 CentOS 6.4 和 mysql 5.1.69,使用 yum 和 CentOS 的 repos 安装,php 5.4.16 使用 yum 和 ius 的 repos 安装。 Edit3 升级到 MySQL 服务器版本:5.5.31 由 IUS 社区项目分发,错误仍然存在。然后将库更改为 mysqlnd,似乎消除了错误。尽管如此,还是需要知道为什么这个错误只是有时才会出现。
使用 PDO 并使用 PDO::ATTR_EMULATE_PREPARES=>false
创建 PDO 对象时,有时会收到以下错误:
Table Name - zipcodes
Error in query:
SELECT id FROM cities WHERE name=? AND states_id=?
SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
File Name: /var/www/initial_install/build_database.php
Line: 547
Time of Error: Tuesday July 2, 2013, 5:52:48 PDT
第 547 行是以下代码的最后一行:
$stmt_check_county->execute(array($data[5],$data[4]));
if(!$county_id=$stmt_check_county->fetchColumn())
$stmt_counties->execute(array($data[5]));
$county_id=db::db()->lastInsertId();
//$stmt_check_county->closeCursor(); //This will fix the error
$stmt_check_city->execute(array($data[3],$data[4]));
几年前我遇到过类似的问题,但从 PHP 5.1 升级到 PHP 5.3(MySQL 可能也更新了),问题神奇地消失了,现在我用 PHP 5.5 解决了。
为什么它只在PDO::ATTR_EMULATE_PREPARES=>false
时才表现出来,并且只有 PHP 的交替版本?
我还发现closeCursor()
也可以修复错误。是否应该在每次 SELECT
查询未使用 fetchAll()
后始终执行此操作?请注意,即使查询类似于 SELECT COUNT(col2)
仅返回一个值,该错误仍然会发生。
编辑顺便说一句,这就是我创建连接的方式。我最近才添加了MYSQL_ATTR_USE_BUFFERED_QUERY=>true
,但是它并不能解决这个错误。 此外,可以按原样使用以下脚本来创建错误。
function sql_error($e,$sql=NULL)return('<h1>Error in query:</h1><p>'.$sql.'</p><p>'.$e->getMessage().'</p><p>File Name: '.$e->getFile().' Line: '.$e->getLine().'</p>');
class db
private static $instance = NULL;
private function __construct() //Make private
private function __clone() //Make private
public static function db() //Get instance of DB
if (!self::$instance)
//tryself::$instance = new PDO("mysql:host=localhost;dbname=myDB;charset=utf8",'myUsername','myPassword',array(PDO::ATTR_EMULATE_PREPARES=>false,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC));
tryself::$instance = new PDO("mysql:host=localhost;dbname=myDB;charset=utf8",'myUsername','myPassword',array(PDO::ATTR_EMULATE_PREPARES=>false,PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=>true,PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC));
//tryself::$instance = new PDO("mysql:host=localhost;dbname=myDB;charset=utf8",'myUsername','myPassword',array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC));
catch(PDOException $e)echo(sql_error($e));
return self::$instance;
$row=array(
'zipcodes_id'=>'55555',
'cities_id'=>123
);
$data=array($row,$row,$row,$row);
$sql = 'CREATE TEMPORARY TABLE temp1(temp_id INT UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (temp_id) )';
db::db()->exec($sql);
$sql='SELECT COUNT(*) AS valid FROM cities_has_zipcodes WHERE cities_id=? AND zipcodes_id=?';
$stmt1 = db::db()->prepare($sql);
$sql ='SELECT temp_id FROM temp1';
$stmt2 = db::db()->prepare($sql);
foreach($data AS $row)
try
$stmt1->execute(array($row['zipcodes_id'],$row['cities_id']));
$rs1 = $stmt1->fetch(PDO::FETCH_ASSOC);
//$stmt1->closeCursor();
syslog(LOG_INFO,'$rs1: '.print_r($rs1,1).' '.rand());
$stmt2->execute();
$rs2 = $stmt2->fetch(PDO::FETCH_ASSOC);
syslog(LOG_INFO,'$rs2: '.print_r($rs2,1).' '.rand());
catch(PDOException $e)echo(sql_error($e));
echo('done');
【问题讨论】:
其他几篇文章。 ***.com/questions/12843886/…,***.com/questions/3725346/…。两者都没有真正回答问题,而是暗示“在某些事情发生变化并且不再起作用之前不要担心它”。 对不起,我刚刚意识到我的 vps 创建了相同的服务器。它仍然应该在 PHP/MySQL 的交替版本上产生错误,或者当 php 不模拟存储过程时会有所不同。我会更新问题以反映新的理解。 我在您的问题中看不到它,但这个问题也存在(并且困扰)任何运行存储过程的人。您不能在存储过程的结果中运行查询,这会使事情变得非常难以解决。 不是一个完整的答案,但我遇到了同样的情况 - 通常的建议没有任何帮助 - 然后我在我的例程中删除了 CREATE TEMPORARY TABLE 的使用,突然问题就消失了。我相信它来自当事务处于活动状态时调用此例程(使用临时表)时。 人们往往会忘记 php 在垃圾清理方面的低效。在类似的情况下, cursor 和 gc_collect_cycles 为我做了。 【参考方案1】:!!! 警告!!!
如果您尝试获取非 SELECT 查询(例如 - UPDATE/INSERT/ALTER/CREATE),也会发生这种情况
【讨论】:
【参考方案2】:我今天也遇到了这个问题,发现我把错误的SQL语句(SELECT
)放到了PDO的exec()
方法中。然后我得出一个结论,我们只能放 write (INSERT
, UPDATE
, DELETE
) SQL 语句而不是 read (SELECT
) SQL 语句方法。
【讨论】:
伙计们,这是大多数时候的根本原因。 => 如果您错误地通过 PDO::exec() 而不是 PDO::query() 运行 SELECT,则无法关闭游标,并且任何后续查询都将失败并显示消息“无法执行查询,而其他无缓冲查询处于活动状态”。 PDO::exec() 返回受影响的行数,一个数字,而不是可以关闭游标的 stmt 对象。因此,如果您觉得 MYSQL_ATTR_USE_BUFFERED_QUERY 没有效果,请尝试在考虑到这一点的情况下查看您的代码【参考方案3】:我几乎有同样的问题。连接到 db 后我的第一个查询返回空结果并删除此错误。启用缓冲区没有帮助。
我的连接代码是:
try
$DBH = new PDO("mysql:host=$hostname;dbname=$db_name", $username, $password,
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8; SET NAMES utf8",
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NUM));
catch(PDOException $e) echo $e->getMessage();
我的解决方案是删除初始命令:
PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8; SET NAMES utf8"
这是一个正确的代码:
try
$DBH = new PDO("mysql:host=$hostname;dbname=$db_name", $username, $password,
array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_NUM));
catch(PDOException $e) echo $e->getMessage();
并且 MYSQL_ATTR_USE_BUFFERED_QUERY 不会被强制为真。它被设置为默认值。
【讨论】:
或者你可以做PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8, NAMES utf8"
。逗号作为分隔符,不重复 SET。 set-statement.html
它对我有用。太感谢了。现在如果你真的想使用PDO::MYSQL_ATTR_INIT_COMMAND
,你必须直接执行它并关闭光标。【参考方案4】:
我遇到了同样的问题,我正在将结果发送到另一个函数中间循环。快速修复是,将所有结果保存在一个数组中(如比尔所说,如果它太大,你还有其他问题要担心),收集数据后,我运行一个单独的循环来一次调用一个函数。
另外,PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 对我不起作用。
【讨论】:
PDO::ATTR_PERSISTENT => true
对我有用【参考方案5】:
MySQL 客户端协议不允许多个查询“进行中”。也就是说,您已经执行了一个查询,并且您已经获取了一些结果,但不是全部——然后您尝试执行第二个查询。如果第一个查询仍有要返回的行,则第二个查询会出错。
客户端库通过在第一次获取时隐式获取第一个查询的所有行来解决此问题,然后后续获取只是迭代内部缓存的结果。这使他们有机会关闭游标(就 MySQL 服务器而言)。这是“缓冲查询”。这与使用 fetchAll() 的工作方式相同,因为这两种情况都必须在 PHP 客户端中分配足够的内存来保存完整的结果集。
不同之处在于缓冲查询将结果保存在 MySQL 客户端库中,因此 PHP 无法访问行,直到您按顺序 fetch() 每一行。而 fetchAll() 会立即为所有结果填充 PHP 数组,允许您访问任意随机行。
不 使用 fetchAll() 的主要原因是结果可能太大而无法容纳您的 PHP memory_limit。但您的查询结果似乎只有一行,所以这应该不是问题。
您可以 closeCursor() 在获取最后一行之前“放弃”结果。 MySQL 服务器收到通知,它可以在服务器端丢弃该结果,然后您可以执行另一个查询。在完成获取给定结果集之前,您不应该 closeCursor()。
另外:我注意到你在循环中一遍又一遍地执行你的 $stmt2,但它每次都会返回相同的结果。根据将循环不变代码移出循环的原则,您应该在开始循环之前执行一次,并将结果保存在 PHP 变量中。因此,无论使用缓冲查询还是 fetchAll(),都无需嵌套查询。
所以我建议您这样编写代码:
$sql ='SELECT temp_id FROM temp1';
$stmt2 = db::db()->prepare($sql);
$stmt2->execute();
$rs2 = $stmt2->fetchAll(PDO::FETCH_ASSOC);
$stmt2->closeCursor();
$sql='SELECT COUNT(*) AS valid FROM cities_has_zipcodes
WHERE cities_id=:cities_id AND zipcodes_id=:zipcodes_id';
$stmt1 = db::db()->prepare($sql);
foreach($data AS $row)
try
$stmt1->execute($row);
$rs1 = $stmt1->fetchAll(PDO::FETCH_ASSOC);
$stmt1->closeCursor();
syslog(LOG_INFO,'$rs1: '.print_r($rs1[0],1).' '.rand());
syslog(LOG_INFO,'$rs2: '.print_r($rs2[0],1).' '.rand());
catch(PDOException $e)echo(sql_error($e));
注意我还使用命名参数而不是位置参数,这使得将 $row 作为参数值数组传递更简单。如果数组的键与参数名称匹配,则只需传递数组即可。在旧版本的 PHP 中,您必须在数组键中包含 :
前缀,但您不再需要它了。
无论如何你都应该使用 mysqlnd。它有更多的功能,更节省内存,并且它的许可证与 PHP 兼容。
【讨论】:
$stmt1->closeCursor();
和 $stmt2->closeCursor();
是多余的,因为您使用的是 fetchAll()
?
我不认为 closeCursor() 是多余的。 fetchAll() 函数不会关闭游标,因此不会释放服务器端资源。
尽管我使用了 fetchAll,但没有 closeCursor 它不起作用,所以我认为它们不是多余的 :)
我遇到了(几乎)同样的问题。如果您有空闲时间,请查看it【参考方案6】:
我希望得到比以下更好的答案。虽然其中一些解决方案可能会“解决”问题,但它们并未回答有关导致此错误的原因的原始问题。
-
设置
PDO::ATTR_EMULATE_PREPARES=>true
(我不想这样做)
设置PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
(对我不起作用)
使用PDOStatement::fetchAll()
(并不总是可取的)
在每个 $stmt->fetch()
之后使用 $stmt->closeCursor()
(这主要是有效的,但是我仍然有几个没有用的情况)
将 PHP MySQL 库从 php-mysql 更改为 php-mysqlnd(如果没有更好的答案,我可能会这样做)
【讨论】:
另外:如果您运行的查询使用PDO::exec()
返回至少一行,则无法关闭游标,任何后续查询都将失败。不要使用PDO::exec()
,使用PDO::query()
,后跟$st->closeCursor()
。
@rustyx 感谢您的评论,这正是导致我的问题的原因!我正在使用 exec() 在循环中执行数据库更新,因为我不需要返回的数据(对于大多数查询,无论如何都没有),但是有像 SELECT DATABASE() 这样的虚拟查询在执行时引发了异常下一个查询,使这个问题很难找到和解决。
在每个 $stmt->fetch() 为我工作后使用 $stmt->closeCursor()。以上是关于MySQL 错误 2014 的原因在其他无缓冲查询处于活动状态时无法执行查询的主要内容,如果未能解决你的问题,请参考以下文章
如何避免此 PDO 异常:在其他无缓冲查询处于活动状态时无法执行查询