Подтвердить что ты не робот

Причины ошибки 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');
4b9b3361

Ответ 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 не работает для меня.