Related
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');
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.
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.
Set PDO::ATTR_EMULATE_PREPARES=>true (I don't wish to do this)
Set PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (didn't work for me)
Use PDOStatement::fetchAll() (not always desirable)
Use $stmt->closeCursor() after each $stmt->fetch() (this mostly worked, however, I still had several cases where it didn't)
Change PHP MySQL library from php-mysql to php-mysqlnd (probably what I will do if no better answer)
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.
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.
!!!
WARNING !!!
This can also happen if you are trying to fetch a non SELECT query (Eg - UPDATE/INSERT/ALTER/CREATE)
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();
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.
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;
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.
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()
'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!
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');
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.
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.
Set PDO::ATTR_EMULATE_PREPARES=>true (I don't wish to do this)
Set PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (didn't work for me)
Use PDOStatement::fetchAll() (not always desirable)
Use $stmt->closeCursor() after each $stmt->fetch() (this mostly worked, however, I still had several cases where it didn't)
Change PHP MySQL library from php-mysql to php-mysqlnd (probably what I will do if no better answer)
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.
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.
!!!
WARNING !!!
This can also happen if you are trying to fetch a non SELECT query (Eg - UPDATE/INSERT/ALTER/CREATE)
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();
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.
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;
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.
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()
'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!
PDO::exec() allows (at least with some drivers such as mysqlnd) to execute several statements at a time.
This works fine, and when I pass several queries to PDO::exec() they all get executed:
$pdo->exec('DROP TABLE a; DROP TABLE b;');
My PDO instance is configured to throw exceptions:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
If the first query fails, it throws an exception as expected:
$pdo->exec('DROP TABLE does_not_exist; DROP TABLE ok;'); // PDOException
But when any subsequent query fails, it silently ignores this fact and you don't seem to have a way to know it:
$pdo->exec('DROP TABLE ok; DROP TABLE does_not_exist;'); // no exception
var_export($pdo->errorInfo()); // array (0 => '00000', 1 => NULL, 2 => NULL)
Is there any way to configure PDO so that exec() throws an exception if any of the statements fail?
Please note that I don't currently have the obviously better option to run each query in its own exec() call, as I'm writing a tool that reads SQL dump files.
Interesting question... I believe (correct me if I'm wrong) that this "multiple" exec will call each exec after each... so once you get exception it is returned and the execution of your queries is stopped.
used example:
(dbname) test contains 2 tables 'a' and 'b'
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "BEFORE:<br/>";
foreach($db->query("show tables;") as $row) echo $row[0] . "<br />";
$a = $db->exec("drop table b; drop table c");
echo "AFTER:<br/>";
foreach($db->query("show tables;") as $row) echo $row[0] . "<br />";
here i get result:
BEFORE:
a
b
AFTER:
a
1 <--- ^.^
which is somehow stupid. but probably the correct solution would be to use a PDO transaction. You should be able to revert changes if some of your code fails. Basically if you start transaction you will turn off autocommit after each query (WITH EXCEPTION OF 3 queries mentioned at the end!!!). Revert or commit will turn autocommit on again.
Try to instead of $db->exec("q1; q2; q3")... something like this:
try {
$db->beginTransaction();
$db->exec("drop table b;"); // -- note at the end of post!
$db->exec("drop table c;");
$db->commit();
} catch (PDOException $e) {
print_r($e);
$db->rollBack();
}
Basically this aproach works. HOWEVER!
be aware that you can not use TRUNCATE TABLE as this statement will trigger a commit just like CREATE TABLE or DROP TABLE.
So if you are handling queries like DROP TABLE etc... your correct solution in this particular case is to use this query instead of simple drop:
SQL: DROP TABLE IF EXISTS `tablename`;
this statement won't trigger exception ;)
Hope it helps ^.^
As mentioned in bug #61613, it is possible to get an exception if any of the statements fails.
The solution is to use emulated prepares (on by default), and PDOStatement::nextRowset():
$pdo = new PDO(...);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// on by default, not necessary unless you need to override a previous configuration
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
try {
$statement = $pdo->query('DELETE FROM unknown_a; DELETE FROM unknown_b;');
// loop through all the statements
while ($statement->nextRowset());
} catch (PDOException $e) {
echo $e->getMessage(), PHP_EOL;
}
If the first statement fails, query() will throw an exception.
If the first statement succeeds and the second statement fails, query() will work fine, and nextRowset() will throw an exception.
Caveat: no further statements are executed after a failed statement. Say you have a SQL string containing 4 statements, and the third one fails:
1st statement: query() will succeed
2nd statement: nextRowset() will succeed and return true
3rd statement: nextRowset() will fail with an exception
4th statement: nextRowset() would return false; the statement is not executed.
If you're using the code above, it stops on first exception anyway.
I have the following in php:
try {
// INSERT FETCHED LIST INTO ARCHIVE
$stmt1 = $sql->prepare('INSERT INTO hsarchive (LIST) VALUES (?)');
$stmt1->bind_param("s",$total);
$stmt1->execute();
$stmt2 = $sql->prepare('TRUNCATE TABLE highscore');
$stmt2->execute();
$sql->rollback();
$stmt1->close();
$stmt2->close();
} catch (Exception $e) {
echo "error";
$sql->rollback();
}
Engine is InnoDB and the connection is started like:
$sql = getSQLAccess();
$sql->autocommit(false);
$sql->begin_transaction();
with getSQLAccess returning an object of the type connection with user, pw etc. in it.
No matter how I spin this, the table is truncated and the list is inserted into the archive. I tried switching around where I close the statements, and as you can see I'm currently not even committing, as I'm trying to figure out why the rollback doesnt work.
Anyone?
EDIT: So this would be the way to go, according to best answer:
try {
// INSERT FETCHED LIST INTO ARCHIVE
$stmt = $sql->prepare('INSERT INTO hsarchive (LIST) VALUES (?)');
$stmt->bind_param("s",$total);
$stmt->execute();
$stmt->close();
$stmt = $sql->prepare('DELETE FROM highscore');
$stmt->execute();
$stmt->close();
$sql->commit();
} catch (Exception $e) {
$sql->rollback();
}
DDL in transactions
Since we've figured out that there are no FK constraints to table highscore - then your issue is caused because since MySQL 5.0.3, TRUNCATE table syntax is equivalent to deletion of all rows logically but not physically
If there are no foreign key constraints to this table (your case) which restricts from doing this, MySQL will produce TRUNCATE operation via fast scheme: it will do DROP table + CREATE table. So while logically it's same to deletion of all rows, it's not the same in terms of how operation is maintained.
Why this is the difference? Because MySQL doesn't support DDL in transactions. More precise, such operations can not be rolled back. For MySQL, DDL operations will cause immediate implicit commit. That is why you see that your TRUNCATE statement: first, is committed even if you don't commit; second, rollback has no effect on it.
Solution
If you still need to rollback your operation, then, unfortunately, you'll need to use DELETE syntax instead of TRUNCATE. Unfortunately - because, obviously, DELETE is much slower than TRUNCATE, because rows will be processed one by one.
I am trying to extract two fields from tables accesscode and student the update the table borrowers with the data that i have extracted from the previous tables.
$q=$db->query("SELECT regnum, accesscode FROM student,student_accesscode WHERE student.id=student_accesscode.studentid");
while($qd=$q->fetch(PDO::FETCH_ASSOC))
{
$access=$qd['accesscode'];
$regnum=$qd['regnum'];
$q2=$db->exec("UPDATE borrowers SET cardnumber='$access' WHERE cardnumber='$regnum'");
if($q2)
{
echo $access.' '. $regnum.'<br/>';
}
else
{
echo'erro....<br/>';
}
}
?>
Appending $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); before $db->query() probably gives you an Exception...
Prior to the $db->setAttribute, you will get this:
PDO::prepare(): 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.
So, instead of fetch(), use fetchAll() with foreach loop, it will make you less insane.
Ref. from php.net
For a statement that you need to issue multiple times, prepare a PDOStatement
object with PDO::prepare() and issue the statement with PDOStatement::execute().
From the PHP exec page at http://www.php.net/manual/en/pdo.exec.php
I don't know if this is the problem, I always use prepare and execute. It could just be for performance reasons. Something to try anyway.