【问题标题】:Autorollback in postgres using PDO使用 PDO 在 postgres 中自动回滚
【发布时间】:2014-05-09 17:12:09
【问题描述】:

我发现 postgres + PDO auto rollbacks 在抛出异常时会发生变化(即使异常被捕获并吞下!)。示例(伪代码):

  $transaction->begin();
  try {
     $manager->insert("INSERT ...");
     try {
       $manager->exec("A QUERY BREAKING SOME DB CONSTRAINT LIKE A UNIQUE INDEX ...");
     } catch (\Exception $ex) {
        // IT IS CAUGHT AND SWALLOWED!
     }
     $transaction->commit();
  } catch (Exception $ex) {
     $transaction->rollback(); // THIS CLEARLY DOES NOT RUN!
  }

在 postgres 中,第一个插入被还原。在mysql中没有。

谁能解释一下这个问题?有没有可能改变这种荒谬的行为?我想自己执行回滚,而不是让 pg 在他认为合适的时候执行。

【问题讨论】:

  • 好吧,经过重新考虑,@samitha 的问题可以用一种非常明智的方式来解释。使用 myisam 引擎,无论是否有回滚,都不会恢复插入。
  • 我手头没有亮光,但我有一个友好的建议。创建一个一致且防错的测试用例,而不是充满假设的伪代码草图。
  • @YourCommonSense 它是 innodb(谁再使用 myisam?)

标签: php postgresql pdo


【解决方案1】:

这不是 PDO 的错,它是 PostgreSQL 事务管理固有的。见:

PostgreSQL 不会回滚事务,而是将其设置为只能回滚的中止状态,并且除了ROLLBACK 之外的所有语句都报告错误:

ERROR: current transaction is aborted, commands ignored until end of transaction block

(我很惊讶官方文档中没有提到这一点;我认为我需要编写一个补丁来改进它。)

所以。当您尝试/捕获并吞下 PDO 中的异常时,您捕获的是 PHP 端异常,但您并没有改变 PostgreSQL 事务处于中止状态的事实。

如果您希望能够吞下异常并继续使用事务,则必须在每个可能失败的语句之前create a SAVEPOINT。如果失败,您必须ROLLBACK TO SAVEPOINT ...;。如果成功,您可以RELEASE SAVEPOINT ...;。这给数据库带来了额外的事务管理开销,增加了往返次数,并且更快地烧毁事务 ID(这意味着 PostgreSQL 必须做更多的后台清理工作)。

通常最好改为设计您的 SQL,这样它在正常情况下不会失败。例如,您可以在客户端验证大多数约束,将服务器端约束视为第二级保证,同时在客户端捕获大多数错误。

如果这样做不切实际,请让您的应用程序具有容错能力,以便它可以重试失败的事务。有时这无论如何都是必要的——例如,您通常不能使用保存点从死锁事务中止或序列化失败中恢复。尽可能缩短容易失败的事务也很有用,只做所需的最少工作,这样您就可以减少跟踪和重复的时间。

所以:在可能的情况下,不要吞下异常,而是在重试循环中运行容易发生故障的数据库代码。确保您的代码记录在出错时重试整个事务所需的信息,而不仅仅是最近的语句。

请记住,任何事务都可能失败:DBA 可能会重新启动数据库以应用补丁,系统可能会因 cron 作业失控而耗尽 RAM,等等。因此,容错应用程序是一种无论如何都是好的设计。

支持您至少使用 PDO 异常和处理异常 - 您已经领先于大多数开发人员。

【讨论】:

  • 因此,当我们进入中止状态(每次抛出异常时都是这种情况)时,唯一可以执行的方法是$transaction->rollback(),但在我的情况下,我并没有像它一样执行它在catch 中,解释器不会去那里作为吞下的例外。是这样吗?因此底线是:永远不要在将发生回滚的 catch 块中隐藏异常。你可否确认?我今天正在寻找保证:)
  • 严格来说,只有在 PHP 中抛出异常由于 PostgreSQL 返回的错误SQLSTATE,事务才会进入中止状态。 client-side-only 异常(如无效的查询参数)有可能使事务保持有效和完整,因为实际上没有任何东西进入服务器。
  • 至于客户端异常处理的细节,我无法从不完整的伪代码示例中进行有用的推理。无论如何,我不知道 PHP 客户端异常的细节和怪癖,因为我很少对 PHP 做任何事情。从你上面勾勒出来的内容来看,看起来当你的约束违反异常发生时,回滚是无法到达的,但是,是的。
  • 您的回答中的最后一件事;您说:“PostgreSQL 不会回滚事务,但会将其设置为只能回滚的中止状态……”。这是可以理解的。我对所有 PHP 语句(但回滚)报告该错误感到满意。我唯一不明白的是,为什么事实上,即使您没有通过 PHP 以编程方式执行事务,事务也会回滚。是不是因为:“当脚本结束或者连接即将关闭时……”(摘自:php.net/manual/en/pdo.transactions.php
  • @nourdine 是的,这是最可能的解释。此外,如果您关闭一个具有打开事务而没有显式提交或回滚的连接,PostgreSQL 它自己将始终回滚它,永远不会提交它。
猜你喜欢
  • 2013-05-13
  • 2012-12-10
  • 2021-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-22
  • 1970-01-01
相关资源
最近更新 更多