【问题标题】:PHP PDO fails when PDO::ATTR_EMULATE_PREPARE = false当 PDO::ATTR_EMULATE_PREPARE = false 时 PHP PDO 失败
【发布时间】:2021-03-05 11:18:59
【问题描述】:

我有一个应用程序,其中我将一个 PDO connection 类作为参数传递给一个使用该 PDO 连接实例与数据库交互的类的参数。

为了创建到数据库的 PDO 连接,我必须使用选项 PDO::ATTR_EMULATE_PREPARES 设置为 false 进行 PDO 连接。使用 PDO 连接的类在执行 SELECT 或 INSERT 语句时没有问题,但在以下语句的情况下:

使用数据库名称;
创建触发器
删除触发器

我收到一个错误:

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 属性进行缓冲。

第二次运行脚本:

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

首先要通过 USE 语句抛出的异常给出另一个异常:

致命错误:未捕获的 RuntimeException:无法执行查询: “如果存在则删除触发器 tr_example_bi_fill;”。 PDO 异常消息: SQLSTATE [HY000]:一般错误:2014 无法执行查询,而 其他无缓冲查询处于活动状态。考虑使用 PDOStatement::fetchAll()。或者,如果您的代码只是 要针对 mysql 运行,您可以通过设置启用查询缓冲 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 属性。

我想问一下如何使用 PDO 连接到数据库的单一实例,其中选项 PDO::ATTR_EMULATE_PREPARES 设置为 false 并能够执行像 USE database-name;DROP TRIGGER ...; 这样的语句CREATE TRIGGER ...;?

关注unbuffered queries 的错误消息让我感到困惑。
IMO 缓冲/非缓冲查询没有问题,使用 $options[\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = false;$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,我相信默认情况下这是正确的。但是USE ...CREATE TRIGGER ...DROP TRIGGER ... 的问题仍然存在。

我正在使用: PHP 7.3.8(cli)(构建:2019 年 8 月 2 日 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 镜像。

【问题讨论】:

  • 问题中没有足够的信息。您能否从这段代码中删除每一个 try-catch 语句,再次运行它并提供您得到的确切输出?
  • 另外,PHP 版本以及 phpinfo() 输出中是否提到“mysqlnd”
  • FWIW 在尝试将 emulate_prepares 设置为 false 并始终让 PDO 将其设置为 true 来处理事情时,我遇到了很多问题。也就是说,您使用的是什么版本的 PHP 和 MySQL,使用的是什么版本的 mysqlnd?
  • @YourCommonSense 我编辑了这个问题,所以更明显的是抛出了什么异常以及何时并在最后添加了 PHP 版本及其扩展。
  • @Dave 最后回答了问题的编辑。

标签: php mysql pdo prepared-statement setattribute


【解决方案1】:

你的代码错了

manual

DROP TRIGGER IF EXISTS tr_example_bi_fill 

【讨论】:

  • 不幸的是,我可能用错误的 echo 语句误导了您(我更改了它)实际查询: $sqlQuery = 'DROP TRIGGER IF EXISTS tr_example_bi_fill;';当 PDO::ATTR_EMULATE_PREPARES 设置为 true 时,它​​是正确的并且运行良好。 USE ... 语句也可以正常工作,但是当 PDO::ATTR_EMULATE_PREPARES 设置为 false 时,它​​们都失败了。
  • 是否有充分的理由为什么它一定是假的?好像是一个bug,存在很久了,没有修复,你应该试试php的新prelereas,如果他们已经修复了jet
  • 将 PDO::ATTR_EMULATE_PREPARES 设置为 FALSE 允许在插入时使用 multiexec Emulation OFF 这在一次插入大量时会更快。
  • 只需关闭第一个连接并为 false 打开第二个连接。如果您必须在两者之间切换,则两者都打开