我发现postgres + PDO 自动回滚以前在抛出异常时会发生变化(即使出现异常和吞噬!)。示例(伪代码):
$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中。
任何人都能对这件事情有所了解吗?是否有可能改变这种荒谬的行为?我想自己完成我的回滚,并且当他认为合适的时候不要去做。
答案 0 :(得分:3)
这不是PDO的错,它是PostgreSQL事务管理所固有的。参见:
PostgreSQL不会回滚事务,但会将其设置为中止状态,只能回滚,并且除ROLLBACK
之外的所有语句都报告错误:
ERROR: current transaction is aborted, commands ignored until end of transaction block
(我很惊讶在官方文档中没有找到这个;我想我需要编写一个补丁来改进它。)
因此。当您在PDO中尝试/ catch并吞下异常时,您正在捕获PHP端异常,但您没有改变PostgreSQL事务处于中止状态的事实。
如果您希望能够吞下异常并继续使用该事务,则必须在每个可能失败的语句之前create a SAVEPOINT
。如果失败,您必须ROLLBACK TO SAVEPOINT ...;
。如果成功,您可以RELEASE SAVEPOINT ...;
。这会给数据库带来额外的开销,用于事务管理,增加往返次数,并加快事务ID的烧毁(这意味着PostgreSQL必须进行更多的后台清理工作)。
通常最好设计您的SQL,以便在正常情况下不会失败。例如,您可以验证客户端的大多数约束,将服务器端约束视为第二级保证,同时捕获客户端的大多数错误。
如果这不切实际,请使您的应用程序容错,以便它可以重试失败的事务。有时这仍然是必要的 - 例如,您通常不能使用保存点从死锁事务中止或序列化失败中恢复。保持容易发生故障的事务尽可能短,只做最少的工作也很有用,这样你就可以更少地跟踪和重复。
所以:在可能的情况下,不要吞下异常,而是在重试循环中运行容易出错的数据库代码。确保您的代码记录了在错误时重试整个事务所需的信息,而不仅仅是最新的语句。
请记住,任何事务都可能失败:DBA可能会重新启动数据库以应用修补程序,系统可能会因为失控的cron作业而耗尽RAM等。因此,容错应用程序是好的设计无论如何。
至少使用PDO异常和处理异常来支持你 - 你已经领先于大多数开发者。