Php 使用PDO在postgres中自动回滚

Php 使用PDO在postgres中自动回滚,php,postgresql,pdo,Php,Postgresql,Pdo,我发现postgres+PDO会在抛出异常时自动回滚以前的更改(即使异常被捕获和吞没!)。示例(伪代码): 在postgres中,第一次插入被还原。在mysql中没有 有人能解释一下这件事吗?有可能改变这种荒谬的行为吗?我希望自己执行回滚,而不是让pg在他认为合适的时候执行。这不是PDO的错,这是PostgreSQL事务管理的固有问题。见: PostgreSQL不会回滚事务,但它会将事务设置为中止状态,在此状态下它只能回滚,并且除了ROLLBACK之外的所有语句都会报告错误: 错误:当

我发现postgres+PDO会在抛出异常时自动回滚以前的更改(即使异常被捕获和吞没!)。示例(伪代码):

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


有人能解释一下这件事吗?有可能改变这种荒谬的行为吗?我希望自己执行回滚,而不是让pg在他认为合适的时候执行。

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

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

错误:当前事务被中止,在事务块结束之前忽略命令

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

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

如果您希望能够吞下异常并继续使用事务,那么必须在每个可能失败的语句之前完成。如果失败,您必须
回滚到保存点。如果成功,您可以
释放SAVEPOINT。这给数据库事务管理带来了额外的开销,增加了往返,并加快了事务ID的读取速度(这意味着PostgreSQL必须做更多的后台清理工作)

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

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

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

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


至少在使用PDO异常和处理异常方面,您已经远远领先于大多数开发人员。

嗯,经过再三考虑,@samitha的问题可以用一种非常合理的方式来解释。使用myisam引擎时,无论是否有回滚,都不会恢复插入。我手边没有灯光,但我有一个友好的建议。不要绘制充满假设的伪代码,而是创建一个一致且防错的测试用例。@YourCommonSense它是innodb(谁再使用myisam?),因此当我们进入中止状态(每次抛出异常时都是这种情况)时,唯一可以执行的方法是
$transaction->rollback()
但是在我的例子中,我没有像在
catch
中那样执行它,而且解释器也不会作为异常情况出现在那里。是这样吗?因此,底线是:永远不要在发生回滚的catch块中隐藏异常。你能证实吗?我今天希望得到保证:)严格来说,只有当PostgreSQL返回的错误
SQLSTATE
引发PHP异常时,事务才会进入中止状态。只有客户端异常(如无效查询参数)才有可能使事务保持有效和完整,因为实际上没有任何内容进入服务器。至于客户端异常处理的细节,我无法从不完整的psueodocode示例中进行有效的推理。无论如何,我不知道PHP客户端异常的细节和怪癖,因为我很少使用PHP做任何事情。从上面的草图可以看出,当发生违反约束的异常时,回滚是不可实现的,是的;您说过:“PostgreSQL不会回滚事务,但它会将事务设置为中止状态,在此状态下它只能回滚…”。这是可以理解的。我很高兴所有的PHP语句(除了回滚)都能报告这个错误。我唯一不能理解的是,在实际情况中,为什么事务会回滚,即使您没有通过PHP编程。可能是因为:“当脚本结束或连接即将关闭时……”(摘自:)@nourdine是的,这是最可能的解释。此外,如果您关闭了一个具有开放事务的连接,而没有显式提交或回滚,则PostgreSQL自身将始终回滚该事务,而不会提交该事务。
  $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!
  }