问题:使用 PDO 在 postgres 中自动回滚

我发现 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 在他认为合适的时候这样做。

解答

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

  • 如何告诉 PostgreSQL 在单个约束失败时不要中止整个事务?

  • 我可以要求 Postgresql 忽略事务中的错误吗

  • 事务出错后回滚

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

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

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

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

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

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

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

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

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

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

Logo

PostgreSQL社区为您提供最前沿的新闻资讯和知识内容

更多推荐