Причины ошибки MySQL 2014 Не удается выполнить запросы, в то время как другие небуферизованные запросы активны
Мой сервер запускает CentOS 6.4 с MySQL 5.1.69, установленным с использованием yum с репозиториями CentOS, и PHP 5.4.16, установленным с помощью yum с ius repos. Редактировать3 Обновлено до версии сервера MySQL: 5.5.31 Распространяется проектом сообщества IUS, и ошибка все еще существует. Затем изменили библиотеку на mysqlnd и, похоже, устранили ошибку. Тем не менее, с этим взад и вперед, нужно знать, почему эта ошибка иногда проявляется.
При использовании PDO и создании объекта PDO с помощью PDO::ATTR_EMULATE_PREPARES=>false
я иногда получаю следующую ошибку:
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
, однако он не излечивает ошибку. Кроме того, можно использовать следующий script как для создания ошибки.
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');
Ответы
Ответ 1
Клиентский протокол MySQL не позволяет выполнять более одного запроса. То есть вы выполнили запрос, и вы получили некоторые результаты, но не все, - тогда вы попытаетесь выполнить второй запрос. Если в первом запросе все еще есть строки для возврата, второй запрос получает ошибку.
Библиотеки клиентов обходятся вокруг этого, выбирая все строки первого запроса неявно при первой выборке, а затем последующие выборки просто перебирают результаты внутри кэширования. Это дает им возможность закрыть курсор (насколько это касается сервера MySQL). Это "буферизованный запрос". Это работает так же, как и с помощью fetchAll(), поскольку оба случая должны выделять достаточно памяти в PHP-клиенте для полного набора результатов.
Разница заключается в том, что буферизованный запрос содержит результат в клиентской библиотеке MySQL, поэтому PHP не может получить доступ к строкам до тех пор, пока вы не выберете() каждую строку последовательно. В то время как fetchAll() сразу заполняет массив PHP для всех результатов, позволяя вам получить доступ к любой случайной строке.
Основная причина не использовать fetchAll() заключается в том, что результат может быть слишком большим, чтобы соответствовать вашему PHP memory_limit. Но, похоже, ваши результаты запроса имеют только одну строку, поэтому это не должно быть проблемой.
Вы можете закрытьCursor(), чтобы "отказаться" от результата, прежде чем вы извлекли последнюю строку. Сервер MySQL получает уведомление о том, что он может отменить этот результат на стороне сервера, а затем вы можете выполнить другой запрос. Вы не должны закрыватьCursor(), пока не закончите заданный набор результатов.
Также: я замечаю, что вы выполняете свой $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.
Ответ 2
Я надеюсь получить лучший ответ, чем следующее. Хотя некоторые из этих решений могут "исправить" проблему, они не отвечают на исходный вопрос о том, что вызывает эту ошибку.
- Установить
PDO::ATTR_EMULATE_PREPARES=>true
(я не хочу этого делать)
- Установить
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY
(не работает для меня)
- Используйте
PDOStatement::fetchAll()
(не всегда желательно)
- Используйте
$stmt->closeCursor()
после каждого $stmt->fetch()
(в основном это работало, но у меня все еще было несколько случаев, когда это не было)
- Измените PHP-библиотеку MySQL с php-mysql на php-mysqlnd (возможно, что я сделаю, если не найду лучшего ответа)
Ответ 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 не принудительно соответствует true. Он установлен по умолчанию.
Ответ 4
У меня была та же проблема, я отправлял результаты в другую функцию mid loop. Быстрое исправление было, сохраните все результаты в массиве (как сказал Билл, если он слишком велик, вам придется беспокоиться о других проблемах), после сбора данных я запускал отдельный цикл для вызова функции по одному.
Кроме того, PDO:: MYSQL_ATTR_USE_BUFFERED_QUERY не работает для меня.