PHP PDO завершается с ошибкой, когда PDO::ATTR_EMULATE_PREPARE = false

#php #mysql #pdo #prepared-statement #setattribute

#php #mysql #pdo #подготовленное заявление #setattribute

Вопрос:

У меня есть приложение, в котором я передаю класс подключения PDO к MySQL 8 в качестве параметра классу, который взаимодействует с базой данных с помощью этого экземпляра подключения PDO.

Для создания PDO-соединения с базой данных я должен использовать параметр PDO::ATTR_EMULATE_PREPARES, для которого для PDO-соединения установлено значение false. Класс, который использует PDO-соединение, не имеет проблем с выполнением операторов SELECT или INSERT, но в случае таких операторов, как:

ИСПОЛЬЗУЙТЕ database-name;
СОЗДАЙТЕ ТРИГГЕР
СБРОСА ТРИГГЕРА

Я получаю сообщение об ошибке:

SQLSTATE[HY000]: Общая ошибка: 2014 не может выполнять запросы, пока активны другие небуферизованные запросы. Рассмотрите возможность использования PDOStatement::fetchAll() . В качестве альтернативы, если ваш код будет выполняться только с mysql, вы можете включить буферизацию запросов, установив атрибут PDO ::MYSQL_ATTR_USE_BUFFERED_QUERY .

Но такие утверждения, как:

СОЗДАТЬ БАЗУ
ДАННЫХ СОЗДАТЬ ТАБЛИЦУ

работает нормально.

Вот пример кода для репликации проблемы:

 <?php

header("Content-Type: text/plain");

$user = '';
$pass = '';
$host = '';
$dbname = 'pdo_snippet';

/**
 * Use at first run, the script fails at USE database-name statement
 * but creates database schema $dbName
 */
$dsn = 'mysql:host=' . $host;

/**
 * Use at second run, baypasses fail at USE database-name statement
 * but fails at DROP TRIGGER IF EXISTS;
 */
// $dsn = 'mysql:dbname=' .$dbname. ';host=' . $host;

$options = [];
$options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
$options[PDO::ATTR_EMULATE_PREPARES] = false; # with true no problem

//---------- CONNECT TO DATABASE SERVER ---------
echo "Connect to database";
try {
    $pdo = new PDO($dsn, $user, $pass, $options);
    
} catch (Exception $e) {
    $msg = 'PDO could not establish connection to dsn: ' . $dsn;
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//----------------------------------------------

//--------- CREATE SCHEMA IF NOT EXISTS --------
$sqlQuery = 'CREATE DATABASE IF NOT EXISTS `' . $dbname . '`;';
echo "Create database $dbname IF NOT EXISTS";
try {
    $stmt = $pdo->query($sqlQuery);

} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//------------------------------------------------

//----------- SELECT DEFAULT DATABASE ------------
echo "SELECT FROM default database";
$sqlQuery = "SELECT DATABASE();";
try {
    $stmt = $pdo->query($sqlQuery);

} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//------------------------------------------------
$defaultDbName = $stmt->fetchColumn();
//------------------------------------------------

//----------- USE datatabase as default ----------
if (empty($defaultDbName)) {
    echo "USE $dbname";
    $sqlQuery = 'USE `' . $dbname . '`;';
    try {
        $stmt = $pdo->query($sqlQuery);
        
    } catch (Exception $e) {
        $msg = 'Could not execute query: "' . $sqlQuery . '"';
        $msg .= '. PDO exception Msg: ' . $e->getMessage();
        throw new RuntimeException($msg);
    }
    echo ", DONE.n";
}
//------------------------------------------------

//------------ CREATE TABLE ----------------------
echo "CREATE TABLE example IF NOT EXISTS";
$sqlQuery = 'CREATE TABLE IF NOT EXISTS `example` (`id` INT AUTO_INCREMENT, `name` VARCHAR(255), PRIMARY KEY(`id`)) ENGINE=InnoDb;';
try {
    $stmt = $pdo->query($sqlQuery);
    
} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//------------------------------------------------

//------------ DROP TRIGGER IF EXISTS ------------
echo "DROP TRIGGER IF EXISTS";
$sqlQuery = 'DROP TRIGGER IF EXISTS `tr_example_bi_fill`;';
try {
    $stmt = $pdo->query($sqlQuery);
    
} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//------------------------------------------------

//--------------- CREATE TRIGGER ---------------------
echo "CREATE TRIGGER";
$sqlQuery = "
    CREATE TRIGGER 
        `tr_example_bi_fill`
    BEFORE INSERT ON 
        `example`
    FOR EACH ROW BEGIN
        SET new.`name` = CONCAT(new.`name`, '_TRIGGER');
    END
    ;
";
echo ", DONE.n";

try {
    $stmt = $pdo->query($sqlQuery);

} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
//------------------------------------------------

//--------------- INSERT INTO --------------------
$value = rand(0,9999);
echo "INSERT INTO TABLE example VALUE '$value'";
$sqlQuery = "INSERT INTO `example` (`name`) VALUES ('$value')";
try {
    $stmt = $pdo->query($sqlQuery);

} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//------------------------------------------------

// --------------- SELECT FROM -------------------
echo "SELECT FROM example";
$sqlQuery = "SELECT `name` FROM `example` ORDER BY `id` DESC LIMIT 1;";
try {
    $stmt = $pdo->query($sqlQuery);

} catch (Exception $e) {
    $msg = 'Could not execute query: "' . $sqlQuery . '"';
    $msg .= '. PDO exception Msg: ' . $e->getMessage();
    throw new RuntimeException($msg);
}
echo ", DONE.n";
//------------------------------------------------

$result = $stmt->fetchColumn();

echo "Result:n";
print_r($result);


echo "n=================n";
 

Если я запускаю его в первый раз с:

 $dsn = 'mysql:host=' . $host;
 

затем я получаю:

Неустранимая ошибка: неперехваченное исключение RuntimeException: не удалось выполнить запрос: «USE
pdo_snippet ;». Сообщение об исключении PDO: SQLSTATE[HY000]: Общая ошибка: 2014 не может выполнять запросы, пока активны другие небуферизованные запросы. Рассмотрите возможность использования PDOStatement::fetchAll() . В качестве альтернативы, если ваш код будет выполняться только с mysql, вы можете включить буферизацию запросов, установив атрибут PDO::MYSQL_ATTR_USE_BUFFERED_QUERY .

Running the script at the second time with:

 $dsn = 'mysql:dbname=' .$dbname. ';host=' . $host;
 

to baypass first the exception thrown by USE statement gives another exception:

Fatal error: Uncaught RuntimeException: Could not execute query:
«DROP TRIGGER IF EXISTS tr_example_bi_fill ;». PDO exception Msg:
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.

Я хотел бы спросить, как использовать подписанный экземпляр подключения PDO к базе данных с параметром PDO::ATTR_EMULATE_PREPARES, установленным в false, и иметь возможность выполнять инструкции типа USE database-name; или DROP TRIGGER ...; CREATE TRIGGER ...; ?

Меня смущает сообщение об ошибке, которое фокусируется на unbuffered queries .
IMO нет проблем с буферизованными / небуферизованными запросами, и использование $options[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false; or $options[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = true; не помогает.
Возможно, вы могли бы прояснить эту ошибку для меня.

После некоторого дополнительного тестирования мне кажется, что проблема может быть как-то связана с небуферизованными запросами, потому что, если при втором запуске скрипта я запускаю его с добавлением:

 $options[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false;
 

тогда скрипт завершается с ошибкой намного раньше, в:

Неустранимая ошибка: Неперехваченное исключение RuntimeException: не удалось выполнить запрос: «СОЗДАТЬ ТАБЛИЦУ, ЕСЛИ ОНА НЕ СУЩЕСТВУЕТ example ( id INT AUTO_INCREMENT, name VARCHAR(255), PRIMARY KEY( id )) ENGINE=InnoDB;». Сообщение об исключении PDO: SQLSTATE[HY000]: Общая ошибка: 2014 не может выполнять запросы, пока активны другие небуферизованные запросы. Рассмотрите возможность использования PDOStatement::fetchAll() . В качестве альтернативы, если ваш код будет выполняться только с mysql, вы можете включить буферизацию запросов, установив атрибут PDO ::MYSQL_ATTR_USE_BUFFERED_QUERY .

не доходя до проблемной линии с. DROP TRIGGER IF EXISTS

Таким образом, похоже, что, по крайней мере, для таких операторов, как CREATE TABLE, решением может быть использование $options[PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = true , которое, как я полагаю, является true по умолчанию. Но все же проблема с USE ... и CREATE TRIGGER ... , DROP TRIGGER ... остается.

Я использую: PHP 7.3.8 (cli) (построен: 2 августа 2019 05:16:32) (NTS ) с расширениями, связанными с MySQL: PDO 7.3.8, mysqli 7.3.8, mysqlnd 5.0.12-dev, pdo_mysq 7.3.8

MySQL 8.0.19

оба основаны на официальных изображениях Docker.

Комментарии:

1. В этом вопросе недостаточно информации. Не могли бы вы, пожалуйста, удалить каждый оператор try-catch из этого кода, запустить его снова и предоставить точный результат, который вы получаете?

2. Кроме того, версия PHP и упоминается ли «mysqlnd» в выводе phpinfo()

3. FWIW У меня было много проблем при попытке использовать emulate_prepares, для которого установлено значение false, и всегда позволять PDO обрабатывать вещи, устанавливая для него значение true. Тем не менее, какие версии PHP и MySQL вы используете и какая версия mysqlnd используется?

4. @Yourcommon Sense Я отредактировал вопрос, чтобы было более очевидно, какое исключение генерируется и когда, и добавил в конце версию PHP с ее расширениями.

5. @Dave ответил редактированием вопроса в конце.

Ответ №1:

Ваш код неверен

См. Руководство

 DROP TRIGGER IF EXISTS tr_example_bi_fill 
 

Комментарии:

1. К сожалению, я, вероятно, ввел вас в заблуждение с помощью неправильного оператора echo (я изменил его), фактический запрос: $SqlQuery = ‘DROP TRIGGER IF EXISTS tr_example_bi_fill ;’; корректен и хорошо работает, когда PDO::ATTR_EMULATE_PREPARES имеет значение true . USE ... оператор также работает нормально, но оба они терпят неудачу, когда PDO::ATTR_EMULATE_PREPARES имеет значение false .

2. есть ли веская причина, по которой оно должно быть false? Кажется, это ошибка, которая существует уже давно и не исправлена, вам следует попробовать новые предварительные версии php, если они исправили это.

3. Установка PDO ::ATTR_EMULATE_PREPARES в значение FALSE позволяет использовать multiexec при эмуляции вставок, это быстрее при одновременной вставке огромного количества.

4. просто закройте первое соединение с помощью и откройте второе для false . Если вам нужно переключаться между обоими, откройте оба