52

My server runs CentOS 6.4 with MySQL 5.1.69 installed using yum with CentOS's repos, and PHP 5.4.16 installed using yum with ius's repos. Edit3 Upgraded to MySQL Server version: 5.5.31 Distributed by The IUS Community Project, and error still exists. Then changed library to mysqlnd, and seems to eliminate the error. Still, with this back and forth, need to know why this error only sometimes manifests.

When using PDO and creating the PDO object using PDO::ATTR_EMULATE_PREPARES=>false, I sometimes get the following error:

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

Line 547 is the last line of:

$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]));

I had a similar problem several years ago, but upgraded from PHP 5.1 to PHP 5.3 (and MySQL probably was updated as well), and the problem magically went away, and now I have it with PHP 5.5.

Why does it only manifest itself when PDO::ATTR_EMULATE_PREPARES=>false, and with only alternating version of PHPs?

I've also found that closeCursor() will also fix the error. Should this always be done after every SELECT query where fetchAll() is not used? Note that the error still occurs even if the query is something like SELECT COUNT(col2) which only returns one value.

Edit By the way, this is how I create my connection. I've only recently added MYSQL_ATTR_USE_BUFFERED_QUERY=>true, however, it doesn't cure the error. Also, the following script could be used as is to create the error.

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)
        {
            //try{self::$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));}
            try{self::$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));}
            //try{self::$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');
4

6 回答 6

90

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 兼容。

于 2013-07-10T23:25:36.233 回答
14

我希望得到比以下更好的答案。虽然其中一些解决方案可能会“解决”问题,但它们并没有回答有关导致此错误的原因的原始问题。

  1. 设置PDO::ATTR_EMULATE_PREPARES=>true(我不想这样做)
  2. 设置PDO::MYSQL_ATTR_USE_BUFFERED_QUERY(对我不起作用)
  3. 使用PDOStatement::fetchAll()(并不总是可取的)
  4. $stmt->closeCursor()在每个之后使用$stmt->fetch()(这主要是有效的,但是,我仍然有几个没有用的情况)
  5. 将 PHP MySQL 库从 php-mysql 更改为 php-mysqlnd (如果没有更好的答案,我可能会做什么)
于 2013-07-10T17:35:10.543 回答
9

我有几乎同样的问题。连接到 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 不强制为真。它被设置为默认值。

于 2016-02-01T22:45:48.260 回答
5

我今天也遇到了这个问题,注意到我SELECT在 PDO 的exec()方法中放入了错误的 SQL 语句()。然后我得出一个结论,我们只能将write ( INSERT, UPDATE, DELETE) SQL 语句而不是read ( SELECT) 语句放入方法中。

于 2020-06-17T10:08:30.750 回答
4

!!!警告 !!!

如果您尝试获取非 SELECT 查询(例如 - UPDATE/INSERT/ALTER/CREATE),也会发生这种情况

于 2020-12-20T09:33:16.687 回答
0

我有同样的问题,我正在将结果发送到另一个函数中间循环。快速修复是,将所有结果保存在一个数组中(如比尔所说,如果它太大,你还有其他问题要担心),收集数据后,我运行一个单独的循环来一次调用一个函数。

此外, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 对我不起作用。

于 2014-12-18T15:21:45.333 回答