56

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');
user1032531
  • 24,767
  • 68
  • 217
  • 387
  • A couple of other posts. http://stackoverflow.com/questions/12843886/when-should-i-use-closecursor-for-pdo-statements, http://stackoverflow.com/questions/3725346/pdo-bindparam-pdo-bindvalue-and-pdo-closecursor. Neither really answers the question, but implies "don't worry about it until something changes and things no longer work". – user1032531 Jul 03 '13 at 02:19
  • Sorry, I just realized my vps creates the same server. Still it should create an error on alternating versions of PHP/MySQL, or differ when php is not emulating stored procedures. I will update question to reflect new understanding. – user1032531 Jul 03 '13 at 02:24
  • I don't see it in your question but this is issue that also exists (and haunts) anybody who runs stored procedures. You cannot run queries within the results of a stored procedure which can make things quite difficult to work around. – JM4 Oct 02 '13 at 19:56
  • 1
    Not a complete answer, but I had the same situation - nothing of the usual advice helped - then I removed the use of CREATE TEMPORARY TABLE in my routine and suddenly the problem was gone. I believe it came from when this routine (that used a temporary table) was invoked when a transaction was active. – Motin Oct 25 '16 at 21:20
  • People tend to forget about how inefficient php is in garbage cleanup. In similar situation, cursor and gc_collect_cycles did it for me. – Jeffz Dec 29 '17 at 20:57
  • Excellent solution for Laravel: https://stackoverflow.com/questions/44426602/laravel-pdo-prepared-statement-cannot-execute-queries-while-other-unbuffered – Sliq Feb 10 '19 at 03:47

10 Answers10

94

The MySQL client protocol doesn't allow more than one query to be "in progress." That is, you've executed a query and you've fetched some of the results, but not all -- then you try to execute a second query. If the first query still has rows to return, the second query gets an error.

Client libraries get around this by fetching all the rows of the first query implicitly upon first fetch, and then subsequent fetches simply iterate over the internally cached results. This gives them the opportunity to close the cursor (as far as the MySQL server is concerned). This is the "buffered query." This works the same as using fetchAll(), in that both cases must allocate enough memory in the PHP client to hold the full result set.

The difference is that a buffered query holds the result in the MySQL client library, so PHP can't access the rows until you fetch() each row sequentially. Whereas fetchAll() immediately populates a PHP array for all the results, allowing you access any random row.

The chief reason not to use fetchAll() is that a result might be too large to fit in your PHP memory_limit. But it appears your query results have just one row anyway, so that shouldn't be a problem.

You can closeCursor() to "abandon" a result before you've fetched the last row. The MySQL server gets notified that it can discard that result on the server side, and then you can execute another query. You shouldn't closeCursor() until you're done fetching a given result set.

Also: I notice you're executing your $stmt2 over and over inside the loop, but it will return the same result each time. On the principle of moving loop-invariant code out of the loop, you should have executed this once before starting the loop, and saved the result in a PHP variable. So regardless of using buffered queries or fetchAll(), there's no need for you to nest your queries.

So I would recommend writing your code this way:

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

Note I also used named parameters instead of positional parameters, which makes it simpler to pass $row as the array of parameter values. If the keys of the array match the parameter names, you can just pass the array. In older versions of PHP you had to include the : prefix in the array keys, but you don't need that anymore.

You should use mysqlnd anyway. It has more features, it's more memory-efficient, and its license is compatible with PHP.

Bill Karwin
  • 538,548
  • 86
  • 673
  • 828
  • Is `$stmt1->closeCursor();` and `$stmt2->closeCursor();` redundant since you are using `fetchAll()`? – user1032531 Jul 12 '13 at 13:20
  • 4
    I don't think closeCursor() is redundant. The fetchAll() function does not close cursors and therefore does not free server-side resources. – Bill Karwin Jul 12 '13 at 17:05
  • Altho i used fetchAll, without closeCursor it did not work, so i second that they are not redundant :) – Radek Apr 07 '17 at 08:58
  • I've stuck on *(almost)* the same problem. Please if you have some free time, take a look at [it](https://stackoverflow.com/questions/45782939/how-can-i-execute-multiple-procedures-continuously) – stack Aug 20 '17 at 14:12
14

I am hoping for a better answer than the following. While some of these solutions might "fix" the problem, they don't answer the original question regarding what causes this error.

  1. Set PDO::ATTR_EMULATE_PREPARES=>true (I don't wish to do this)
  2. Set PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (didn't work for me)
  3. Use PDOStatement::fetchAll() (not always desirable)
  4. Use $stmt->closeCursor() after each $stmt->fetch() (this mostly worked, however, I still had several cases where it didn't)
  5. Change PHP MySQL library from php-mysql to php-mysqlnd (probably what I will do if no better answer)
user1032531
  • 24,767
  • 68
  • 217
  • 387
  • 3
    Also: if you're running a query that returns at least one row using `PDO::exec()`, there is no way to close the cursor and any subsequent query will fail. Don't use `PDO::exec()`, use `PDO::query()` followed by `$st->closeCursor()`. – rustyx Mar 06 '15 at 13:01
  • 1
    @rustyx thanks for that comment, that was exactly what was causing my problem! I was executing database updates in a loop using exec() because I didn't need the returned data (and for most queries there was none anyway), but there were dummy queries like SELECT DATABASE() which threw the exception upon execution of the next query, making this problem very hard to find and fix. – jurchiks Sep 14 '15 at 17:34
  • 2
    Use $stmt->closeCursor() after each $stmt->fetch() worked for me. – Jam May 31 '17 at 12:04
10

I have almost same problem. My first query after connection to db return empty result and drop this error. Enabling buffer doesn't help.

My connection code was:

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(); }

Solution in my way was to remove initial command:

PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8; SET NAMES utf8"

Here is a correct code:

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(); }

And MYSQL_ATTR_USE_BUFFERED_QUERY is not forced to true. It's set as default.

o139
  • 854
  • 2
  • 8
  • 20
  • 8
    Or you could just do `PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET utf8, NAMES utf8"`. Comma as separator and without repeating SET. [set-statement.html](http://dev.mysql.com/doc/refman/5.7/en/set-statement.html) –  Mar 15 '16 at 08:41
  • It works for me. thank you so much. Now if you really want to use the `PDO::MYSQL_ATTR_INIT_COMMAND` you will have to execute it directly and close the cursor. – F. Dakia Sep 09 '20 at 13:08
5

I also experienced this problem today and noticed that I put wrong SQL statement (SELECT) into PDO's exec() method. Then I came to a conclusion that we can only put write (INSERT, UPDATE, DELETE) SQL statements instead of read (SELECT) ones to the method.

yunhasnawa
  • 815
  • 1
  • 14
  • 30
  • 1
    guys, this is most of the time the ROOT CAUSE. => if you're wrongly running a SELECT via PDO::exec() instead of PDO::query(), there is no way to close the cursor and any subsequent query will fail with that message "Cannot execute queries while other unbuffered queries are active". PDO::exec() returns the number of affected rows, a number, not a stmt object on which you can close cursor. so if you feel like MYSQL_ATTR_USE_BUFFERED_QUERY has no effect, try looking at you code with this in mind – Nadir Apr 26 '21 at 07:16
4

!!! WARNING !!!

This can also happen if you are trying to fetch a non SELECT query (Eg - UPDATE/INSERT/ALTER/CREATE)

M_R_K
  • 5,929
  • 1
  • 39
  • 40
1

if anybody is here, with error while creating tables
this also happens if you try to execute create 2 tables in single query;
this error was thrown when i fired below query;

$q24="create table if not exists table1 like template1;create table if not exists table2 like template2;";
$s24=$link->prepare($q24);
$s24->execute();

seems tables are to be created separately;

$q1="create table if not exists table1 like template1;";
$s1=$link->prepare($q1);
$s1->execute();
//and
$q2="create table if not exists table2 like template2;";
$s2=$link->prepare($q2);
$s2->execute();
sifr_dot_in
  • 3,153
  • 2
  • 33
  • 42
0

I had the same problem, I was sending results to another function mid loop. Quick fix was, save all results in an array (like Bill stated, if it's too large you have other issues to worry about), after collecting the data, I ran a separate loop to call the function one at a time.

Also, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY did not work for me.

H4x4L
  • 9
  • 2
0

I had the same problem and solved it by removing all initial requests related to the character set. so I started from

$con = new \PDO(self::getDriver() . ":host=" . self::getHost() . ":".self::getPort()."; dbname=" . self::getName() . ";charset=utf8", self::getUser(), self::getPassword(), array( \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8;SET SESSION time_zone ='+01:00'"));

to

$con = new \PDO(self::getDriver() . ":host=" . self::getHost() . ":".self::getPort()."; dbname=" . self::getName() . ";charset=utf8", self::getUser(), self::getPassword(), array( \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,\PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION time_zone ='+01:00'"));

so I removed the command SET NAMES utf8;

  • Why you all are so inclined to that MYSQL_ATTR_INIT_COMMAND? Why not run this query simply using $con->query("SET SESSION time_zone ='+01:00'");? – Your Common Sense May 14 '22 at 07:58
  • That's true, but I use several connections in my system and so I don't want to come back and declare an initialization every time I switch from one DB to another – King Saozer May 14 '22 at 08:03
  • How it's even relevant? you can have any number of connections, the question here is how to run additional queries on connect, via MYSQL_ATTR_INIT_COMMAND or explicitly using query() – Your Common Sense May 14 '22 at 08:15
  • Well, okay, I didn't understand it that way – King Saozer May 14 '22 at 12:58
0

I got this error when I accidentally called execute twice, one explicitly and one that was hidden in another call (PDO class). After removing the first execute, the error was gone.

-1

The main reason behind this error is that MySQL is trying to run 'exec' instead of 'execute' and vice versa.

There are two PDO statements that are there to execute queries PDO::exec() and PDO::execute(), both are not the same.

PDO::exec() is designed to execute commands and queries that do not produce a result set.

Ex: SET, UPDATE, INSERT, DELETE etc.

PDO::execute() is designed to execute commands and queries that produce a result set.

Ex: SELECT, CALL, SHOW, OPTIMIZE, EXPLAIN etc.

If you use these commands in wrong place, your will be ended up with this error.

Solution:

Be careful where to use PDO::exec() and PDO::execute()

In my case for Laravel I changed my query from

DB::select("DELETE FROM " . env('DB_PREFIX') . 'products WHERE product_id = ' . $product->id); // Internally Laravel will run PDO::execute() enter image description here 'select' method

to

DB::table('product_currency')->where('product_id', $product->id)->delete(); // Internally Laravel will run PDO::exec()

Hope this gives some more clarification!