13

I have the following code:

$dbh = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbh->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $dbh->prepare("SELECT 1");
$stmt->execute();
$result = $stmt->fetch();

$stmt->execute();
$result = $stmt->fetch();

$stmt = $dbh->prepare("SELECT 1");
$stmt->execute();
$result = $stmt->fetch();

However, for some reason I get the following error when executing the second prepared statement:

Fatal error: Uncaught exception 'PDOException' with message '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.'

I know what this error means and how to fix it (either doing unset($stmt); or $stmt->closeCursor();), so I am not looking for a solution of how to get it to work. From what I understand it is usually caused by doing fetch instead of fetchAll and not fetching all the results. However in this case, there is only one result and it is being fetched. Also, if I only execute the first prepared statement once, the error does not occur. It only happens when the first statement is executed twice. It also only happens when PDO::ATTR_EMULATE_PREPARES is false.

So my question is, what is causing the above error to occur in this case? It doesn't appear to be any different than any other query I've ever executed.

I have tested this on two Ubuntu 13.10 servers, Debian and CentOS and all produce the same error using the default packages.

Edit:

To answer Ryan Vincent's comment, I am a complete mysqli noob, but I believe what I have below is roughly equivalent to the above example. Please correct me if I'm wrong. However it produces no errors, so it would appear to be a PDO-only error:

$mysqli = new mysqli($host, $user, $pass, $dbname);
if ($mysqli->connect_errno) {
    die("Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error);
}

if (!($stmt = $mysqli->prepare("SELECT 1"))) {
     die("Prepare 1 failed: (" . $mysqli->errno . ") " . $mysqli->error);
}

if (!$stmt->execute()) {
    die("Execute 1 failed: (" . $stmt->errno . ") " . $stmt->error);
}
$stmt->store_result();
$stmt->bind_result($col1);
$stmt->fetch();

if (!$stmt->execute()) {
    die("Execute 2 failed: (" . $stmt->errno . ") " . $stmt->error);
}
$stmt->store_result();
$stmt->bind_result($col1);
$stmt->fetch();

if (!($stmt = $mysqli->prepare("SELECT 1"))) {
    // The following line is what fails in PDO
    die("Prepare 2 failed: (" . $mysqli->errno . ") " . $mysqli->error);
}

if (!$stmt->execute()) {
    die("Execute 3 failed: (" . $stmt->errno . ") " . $stmt->error);
}
$stmt->store_result();
$stmt->bind_result($col1);
$stmt->fetch();
Mike
  • 23,542
  • 14
  • 76
  • 87
  • Did you try to read the message past fetchAll part? – Your Common Sense Apr 02 '14 at 07:15
  • @YourCommonSense Yes I did. Like I said, I'm just wondering what is causing the error in this case. Why does the other unbuffered query stay active if it only has one row and I'm fetching it? – Mike Apr 02 '14 at 07:17
  • Tried to reproduce your case but failed. You need a cleaner test case. – Your Common Sense Apr 02 '14 at 07:28
  • @YourCommonSense I'm not sure what you mean by "cleaner". – Mike Apr 02 '14 at 07:35
  • One that can be reproduced by someone else. Including only conventional tools, full code and, may be, dump (if no virtual queries like "select 1" will be able to demonstrate the problem). – Your Common Sense Apr 02 '14 at 08:09
  • nextRowset() doesn't mean there are more rows. it means there are more rowsets. the issue seems quite interesting. – Your Common Sense Apr 02 '14 at 08:15
  • @YourCommonSense added test case. – Mike Apr 02 '14 at 08:29
  • thank you. just run it and there were no errors – Your Common Sense Apr 02 '14 at 08:31
  • a totally wild guess. don't you have any triggers in that database? – Your Common Sense Apr 02 '14 at 08:33
  • 1
    `SHOW TRIGGERS` returns an empty result set, so I guess not. And the test case above was a freshly created database. – Mike Apr 02 '14 at 08:35
  • wow! managed to get the error after setting buffering attr to false – Your Common Sense Apr 02 '14 at 08:37
  • 1
    In this case, I get the error whether it's true or false, but I know it's set to true for the rest of the app because I start getting errors all over the place when I set it to false. This is the *only* place that I get this error when buffering is true. – Mike Apr 02 '14 at 08:41
  • Well, it looks extremely strange then. BTW, does adding extra fetch solve the problem? (i hope - not) – Your Common Sense Apr 02 '14 at 08:53
  • Yes, it does solve the problem. Of course, it returns false. – Mike Apr 02 '14 at 08:57
  • 1
    _“However in this case, there is only one result and it is being fetched”_ – that does not matter, because at this point nobody _knows_ that this is the only result record. (Except you maybe, because you know that you put `LIMIT 1` in the query … but the rest of the parties involved does not care.) – _“This makes no sense to me at all since the error basically means that there are more rows”_ – no, it does not mean that; it means that it is not clear at this point _if_ there are any more result records. Calling `nextRowset` “fixes” this because _now_, after this returned false, it _is_ clear. – CBroe Apr 02 '14 at 08:58
  • @CBroe Why would this only happen when the prepared statement is executed more than once? – Mike Apr 02 '14 at 09:06
  • Because of what the error message already told you: _“Cannot execute queries while other unbuffered queries are active.”_ Because the same connection is used, sending data for query #1 and #2 in alternating order over it is simply not possible (not in the way it is implemented). – CBroe Apr 02 '14 at 09:54
  • @CBroe did you try the last example from the post? – Your Common Sense Apr 02 '14 at 09:58
  • @YourCommonSense: You mean `Edit #4`? That works for me as long as MYSQL_ATTR_USE_BUFFERED_QUERY is set to `true` – changing it to `false` brings the error as expected. – CBroe Apr 02 '14 at 10:19
  • @CBroe the problem is it throws error for the OP even with true. thus, no canonical explanation applies – Your Common Sense Apr 02 '14 at 10:21
  • Well, as I said, it doesn’t for me … so I can only say, this example does not reproduce the problem (might be incomplete, or depend on other stuff we don’t know about yet). – CBroe Apr 02 '14 at 10:23
  • @CBroe in the last edit, that is literally the entire file with the exception of the DB credentials defined just before it. I also tested it on two Ubuntu servers (both 13.10) and one Debian server (7.4) and they all produce the same error. Perhaps it is Debian-specific behavior. – Mike Apr 02 '14 at 20:05
  • Maybe there is something in the (MySQL) server config that doesn’t allow you to use buffered queries or something … – CBroe Apr 02 '14 at 20:19
  • @CBroe The thing is that if I set MYSQL_ATTR_USE_BUFFERED_QUERY to false, things screw up in other places whereas when it is set to true I have no problems except when doing multiple executions of a PDOStatement like in this example, which would lead me to believe that MYSQL_ATTR_USE_BUFFERED_QUERY is actually true. – Mike Apr 02 '14 at 20:30
  • Weird. I just did a fresh VirtualBox install of the most recent CentOS, installed MySQL and PHP and I get exactly the same error when executing the above example by command line while commenting out the second `execute` makes it work. I don't know how you guys aren't able to reproduce this. – Mike Apr 02 '14 at 20:56
  • 1
    Have you tried the same example code using the 'mysqli' class? My understanding that they (PDO and mysqli) use different drivers in PHP. According to the documentation, you are doing the 'right things'. – Ryan Vincent Apr 05 '14 at 19:58
  • @RyanVincent See my edit #6 – Mike Apr 05 '14 at 22:32
  • 1
    I am so guessing, but it is interesting that you do two identical 'prepare' statements using PDO and it goes 'thud' regularly. I suspect that there is 'optimization' logic in the driver that you are running into. So, in your PDO example, either unset the first '$stmt' variable before the second prepare, or use a new $stmtAnother' variable that will convince the driver to rebuild the query. This is all speculation. It is my **opinion**, it is **free** and worth **exactly** what you paid for it. – Ryan Vincent Apr 05 '14 at 22:51
  • At least it gives you something else to check the PDO against. was worth the comment i think. There will be lots of experts along later to explore or dismiss this approach. Whatever, we will all learn more. or not. – Ryan Vincent Apr 05 '14 at 23:11
  • 1
    @RyanVincent Setting multiple variables does not have any effect. However doing `unset` on the first PDOStatement makes it work. I mentioned this in my question. – Mike Apr 05 '14 at 23:50
  • I genuinely do not know what to say to you. There are people with more experience than myself with this stuff. They have already invested a lot of time with you. I have added everything that i think is useful. And that is that the results from 'mysqli' are not the same as with PDO. even with the same queries. Now, it can be something or nothing. You need to talk to the experts. – Ryan Vincent Apr 06 '14 at 00:14
  • Personally, i would ensure that the program works. And you already have enough information on how to do that. I would worry about the why it doesn't work as i expect later. i remind you: these are my opinions, they are free. I do not 'carry the can' if it goes wrong. You do. None of us giving advice 'carry the can'. – Ryan Vincent Apr 06 '14 at 00:20
  • @RyanVincent Thank you for your comments. The program works fine. I knew how to make it work when I asked the question, which is why the question asks what causes the error and not how to fix it. – Mike Apr 06 '14 at 00:29
  • Thanks for listening to me and exploring the options. there are possible bugs in the software. Goodness, how unlikely is that! ;-/ I suspect that there is no satisfactory answer to this issue / situation. Was a joy talking with you. – Ryan Vincent Apr 06 '14 at 00:50
  • Mike, may I suggest that you rework your question so as to make it more straight to the point? This is quite lengthy and I would have been put off had @YourCommonSense not put a huge bounty on it. Future readers might appreciate. IMHO, Edit 4 and 5 describe the problem entirely. – RandomSeed Apr 07 '14 at 08:21

4 Answers4

13

Oddly enough, the PHP packages provided by Ubuntu are not compiled with the Mysql native driver, but with the old libmysqlclient instead (tested on Ubuntu 13.10 with default packages):

<?php
echo $dbh->getAttribute(PDO::ATTR_CLIENT_VERSION); // prints "5.5.35", i.e MySQL version
// prints "mysqlnd (...)" when using mysqlnd

Your very test case ("Edit 4", with setAttribute(MYSQL_ATTR_USE_BUFFERED_QUERY, true)) works as expected with PHP 5.5.3 manually compiled with mysqlnd with:

./configure --with-pdo-mysql=mysqlnd # default driver since PHP v5.4

... but fails with:

bash> ./configure --with-pdo-mysql=/usr/bin/mysql_config

It quite odd that it fails only if the first statement is executed twice; this must be a bug in the libmysqlclient driver.

Both drivers fail as expected when MYSQL_ATTR_USE_BUFFERED_QUERY is false. Your Common Sense already demonstrated why this is expected behaviour, regardless of the number of rows in the result set.

Mike found out that the current workaround is installing the php5-mysqlnd package instead of the Canonical-recommended php5-mysql.

Community
  • 1
  • 1
RandomSeed
  • 29,301
  • 6
  • 52
  • 87
  • Actually, it works for mysqli (see my edit 5), but not PDO. Is it possible that PDO uses libmysqlclient and mysqli uses mysqlnd on the same server? – Mike Apr 07 '14 at 06:41
  • Err... I meant "libmysqlclient" at the end (fixed). Yes, you can very well compile your PHP with `./configure --with-pdo-mysql=/usr/bin/mysql_config --with-mysqli=mysqlnd`. Let me test how this behaves. – RandomSeed Apr 07 '14 at 06:46
  • When I do `mysqli_get_client_info()` it also returns `5.5.35` (without mentioning mysqlnd) so it still looks like if there's a bug it's somewhere in PDO, not in libmysqlclient. – Mike Apr 07 '14 at 06:58
  • Yes, I just realised the same driver is used for all API's (it would be quite weird otherwise). I'd say there is a bug *either* in PDO or *libmysqlclient*, because PDO works just as expected with *mysqlnd*. I would file a bug at PHP, perhaps, even though I don't think it will be fixed, the old driver being kind of deprecated. Alternatively, let's suggest the Ubuntu people to update their compilation scripts. – RandomSeed Apr 07 '14 at 07:09
  • The [manual](http://www.php.net/manual/en/mysqli.overview.php#mysqli.overview.mysqlnd) says "The mysql extension, the mysqli extension and the PDO MySQL driver can each be individually configured to use either libmysqlclient or mysqlnd", so there must be a way. Not that it really matters because it appears I'm using libmysqlclient for both. And I actually did file a [bug report](https://bugs.php.net/bug.php?id=67004) a few days ago. Feel free to add to it. – Mike Apr 07 '14 at 07:14
  • And about recommending to the Ubuntu people to update their compilation scripts, I agree completely. It appears Debian as well chose to use the old driver as well even though PHP is 5.4.4. – Mike Apr 07 '14 at 07:17
  • *"it would be quite weird if the Ubuntu guys had compiled PHP with two different drivers"* ;) – RandomSeed Apr 07 '14 at 07:21
  • @Mike to test for mysqlnd, you have to run phpinfo() and watch pdo_mysql section – Your Common Sense Apr 07 '14 at 07:21
  • btw, to me 5.5.53 is apparently looks like a mysql wersion, irrelevant to client driver – Your Common Sense Apr 07 '14 at 07:28
  • 3
    @RandomSeed I just realized that there is a `php5-mysqlnd` package for Ubuntu, however `php5-mysql` (which uses libmysqlclient) has the little "recommended by Canonical" icon in the package managers. Installing it gets rid of the error, as you found. – Mike Apr 07 '14 at 07:36
  • @YourCommonSense I think RandomSeed was right about the output to `PDO::ATTR_CLIENT_VERSION`. When using libmysqlclient it appears to output the MySQL version, but when using mysqlnd it produces something like "mysqlnd 5.0.11-dev - 20120503 - $Id: 40933630edef551dfaca71298a83fad8d03d62d4 $". – Mike Apr 07 '14 at 07:46
  • @Mike thanks, I had to test it myself before commenting. Well, it seems we have found the cause? – Your Common Sense Apr 07 '14 at 07:55
  • @Mike Nice catch with the `php5-mysqlnd` package. Having found the cause, I am still looking forward to seeing how the PHP team will respond. – RandomSeed Apr 07 '14 at 08:09
  • @RandomSeed I doubt there will be any response other than "libmysql is obsoleted, use mysqlnd". I'd rather ask Ubuntu guys for making mysqlnd default choice, like they have it in Fedora. – Your Common Sense Apr 07 '14 at 08:12
  • @YourCommonSense Possibly they could confirm the bug exists but set it as "won't fix". At least then it may (further) dissuade people from using libmysql binaries by default in their distributions. – Mike Apr 07 '14 at 08:18
  • By the way, Debian appears to [recommend php5-mysql](https://wiki.debian.org/LaMp#The_.22P.22_part) instead of php5-mysqlnd as well. – Mike Apr 07 '14 at 08:22
3

This is not necessarily the answer to this question, but this may help somebody in the future.

I came across exactly the same error and it took hours to discover what was wrong. It turned out it was just a extremely minor syntax issue all along. If you're not actually using any buffering, but still have this error, like I did, this could be your issue - so check your code.

I was doing my normal database queries when I came across this error -- not purposely using any buffering techniques -- so I highly doubted it had anything to do with buffering. I read every SO question about it and looked deeper in to it.

This was my STUPID syntax issue:

$SQL = "UPDATE articles SET
            topicID = :topic;    <-------- semicolon - woops!
            heading = :heading,
            subheading = :subheading,
            keywords = :keywords,
            rawContent = :rawContent,
            content = :content,
            ...
            ...

This resulted in me getting this buffering error. I fixed the code and it went away. What was most annoying, was the fact the PDO error was pointing at another query, the next query, but that query was in a function elsewhere in the code, and that through me well off course for a while!

TheCarver
  • 19,391
  • 25
  • 99
  • 149
2

It seems that you have PDO::MYSQL_ATTR_USE_BUFFERED_QUERY set to FALSE.

And in such a case it is obligatory to make sure that there are no more rows pending for the retrieval. To do so one to run fetch() one extra time, as it seems that fetch() returning false is "releasing" non-buffered resultset somehow. Without such extra call non-buffered resultset remains locked and causing "Commands out of sync" error

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • If you fetch one row at a time from the 'result set' then Mysql cannot close the result set until you fetch the last row from it. Which means you need to fetch twice even if only one row is returned from the query as you cannot tell there are no more rows until you try and read them. – Ryan Vincent Apr 04 '14 at 20:06
  • @RyanVincent yes, but only if result weren't buffered. If it was, only one fetch is enough – Your Common Sense Apr 04 '14 at 20:09
  • sorry, i am 'hard of thinking' currently. – Ryan Vincent Apr 04 '14 at 20:12
  • Just try it and see, if you don't like the sound. – Your Common Sense Apr 04 '14 at 20:13
  • I was wrong! I have read the instructions for the 'mysqli' and 'PDO' API for the version of 'mysql' that i use here (5.5.16 on windows XP) and you are quite correct – Ryan Vincent Apr 06 '14 at 02:36
0

Just to complete the list of possible mistakes causing this issue ... because I was loosing my hair on this I want to share my solution/finding with you.

In my case I've tried to sent several statements to the database with PDO::exec

e.g.

self::$objDatabase->exec( "SELECT id from testtable; UPDATE testtable SET name = 'example';" );

Only 1 SQL Statement in 1 PDO::exec is allowed and save.

Thomas
  • 81
  • 1
  • 4