【问题标题】:Is there a way to throw exceptions when any statement fails in PDO::exec()?当 PDO::exec() 中的任何语句失败时,有没有办法抛出异常?
【发布时间】:2014-07-15 22:04:25
【问题描述】:

PDO::exec() 允许(至少对于某些驱动程序,例如 mysqlnd)一次执行多个语句。

这很好用,当我向PDO::exec() 传递几个查询时,它们都会被执行:

$pdo->exec('DROP TABLE a; DROP TABLE b;');

我的 PDO 实例被配置为抛出异常:

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

如果第一个查询失败,它会按预期抛出异常:

$pdo->exec('DROP TABLE does_not_exist; DROP TABLE ok;'); // PDOException

但是当任何后续查询失败时,它会默默地忽略这个事实,你似乎没有办法知道它:

$pdo->exec('DROP TABLE ok; DROP TABLE does_not_exist;'); // no exception
var_export($pdo->errorInfo()); // array (0 => '00000', 1 => NULL, 2 => NULL)

是否有任何方法可以配置 PDO,以便 exec()任何 语句失败时抛出异常?

请注意,由于我正在编写一个读取 SQL 转储文件的工具,因此我目前没有明显更好的选择来在其自己的 exec() 调用中运行每个查询。 p>

标签: php pdo


【解决方案1】:

有趣的问题...我相信(如果我错了,请纠正我)这个“多个”执行程序将在每个执行程序之后调用每个执行程序...因此,一旦您遇到异常,它就会返回并且您的查询的执行将停止.

使用示例: (dbname) test 包含 2 个表 'a' 和 '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 />";

我在这里得到结果:

BEFORE:
a
b
AFTER:
a
1              <--- ^.^

这有点愚蠢。但可能正确的解决方案是使用 PDO 事务。如果您的某些代码失败,您应该能够恢复更改。基本上,如果您开始事务,您将在每次查询后关闭自动提交(除了最后提到的 3 个查询!!!)。恢复或提交将再次打开自动提交。

尝试代替 $db->e​​xec("q1; q2; q3")... 类似这样的东西:

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();
}

基本上这种方法有效。但是!

请注意,您不能使用 TRUNCATE TABLE,因为此语句会像 CREATE TABLE 或 DROP TABLE 一样触发提交。

因此,如果您正在处理 DROP TABLE 等查询...在这种特殊情况下,您的正确解决方案是使用此查询而不是简单的 drop:

SQL: DROP TABLE IF EXISTS `tablename`;

此语句不会触发异常 ;)

希望对你有所帮助^.^

【讨论】:

  • 确实在其中一个查询失败后,其他的都不会执行。我的问题是,正如问题中所述,我无法发出单独的 exec() 调用,因为我正在执行一个 .sql 转储文件,并且无法控制其内容!
【解决方案2】:

bug #61613 中所述,如果任何 语句失败, 可能会出现异常。

解决方案是使用emulated prepares(默认开启)和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;
}

如果第一条语句失败,query() 会抛出异常。

如果第一条语句成功,第二条语句失败,query() 会正常工作,nextRowset() 会抛出异常。

警告:在一条失败的语句之后不再执行任何语句。假设您有一个包含 4 条语句的 SQL 字符串,而第三条语句失败:

  • 第一条语句:query() 将成功
  • 第二条语句:nextRowset() 将成功并返回true
  • 第三条语句:nextRowset() 将失败并出现异常
  • 第四条语句:nextRowset() 将返回false语句未执行

如果您使用上面的代码,它无论如何都会在第一个异常时停止。

【讨论】:

  • 您只是忘记了准备好的语句不是必需的,您需要turn the Emulation on才能运行多个查询。
  • 谢谢!我更新了我的答案以反映对模拟准备的需要。不过,我不确定您对准备好的语句不是必需的意思是什么:如果您使用直接的query() 并在第一条语句上出现异常,则您无权访问准备好的语句对象来调用nextRowset()上,你呢?
  • 你这样做,prepare() 和 query() 在这方面是相等的
  • 对,如果发生异常,query() 不会返回 PDOStatement,这让我感到困惑,所以没有什么可以调用 nextRowset(),但我们不在乎这个case 因为无论如何在异常之后进一步的语句结果都不可用,所以我们不关心没有PDOStatement
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-13
  • 1970-01-01
  • 2018-03-02
  • 1970-01-01
  • 2010-12-08
  • 2021-06-16
相关资源
最近更新 更多