PHP与PostgreSQL连续插入问题:如何实现失败回滚?
问题描述
我有两个通过外键关联的表。注册用户时,需先将用户信息插入info表,再将凭证信息插入access表。但当第二次插入(access表)因任意原因失败时,希望撤销第一次插入的info表数据。
以下是部分代码:
// Data going to table "info" $info = array( "fname" => $_POST["fname"], "lname"=> $_POST["lname"] ); // if insert into table "info" is successfull if ($lastInsertID = $this->model->addUser("info", $info)) { // Data going to table "access" $access = array( "id" => $lastInsertID, "password" => $_POST["password"], "group" => "users", "privilege" => 0, ); // insert into table "access" if ($this->model->addUser("access", $access)) { echo json_encode(array("success")); } else { // if insert into table "access" fails // remove the last insert into table "info" $this->model->deleteUser("info", $lastInsertID); echo json_encode(array("error", "access")); } } else echo json_encode(array("error", "info"));
问题在于,当access表的插入失败时,删除info表对应数据的代码并未执行,仅返回SQLSTATE异常,且info表中的记录仍残留。请问如何改进代码?或有没有更好的方式处理PHP与PostgreSQL的连续插入?
解决方案
核心问题分析
你的代码里addUser("access", $access)执行失败时直接抛出了SQL异常,导致后续的deleteUser代码根本没机会执行——异常中断了代码流程,直接跳出了当前的逻辑块。
标准解决方案:使用数据库事务
PostgreSQL支持事务机制,这是处理此类“原子性操作”的最优方案,能保证一系列SQL操作要么全部提交生效,要么全部回滚撤销,从根源上避免脏数据残留。
改造步骤
- 先开启数据库事务
- 执行
info表的插入操作 - 基于
info表的自增ID,执行access表的插入操作 - 如果两步操作都成功,提交事务
- 只要任意一步操作失败(包括抛出异常),立即回滚事务
改造后的示例代码
假设你的model类持有PDO数据库连接实例(命名为db),代码调整如下:
// 开启事务(自动关闭PDO的自动提交) $this->model->db->beginTransaction(); try { // 插入info表 $info = array( "fname" => $_POST["fname"], "lname" => $_POST["lname"] ); $lastInsertID = $this->model->addUser("info", $info); if (!$lastInsertID) { throw new Exception("插入info表失败"); } // 插入access表 $access = array( "id" => $lastInsertID, "password" => $_POST["password"], "group" => "users", "privilege" => 0, ); $accessInsertSuccess = $this->model->addUser("access", $access); if (!$accessInsertSuccess) { throw new Exception("插入access表失败"); } // 所有操作成功,提交事务 $this->model->db->commit(); echo json_encode(array("success")); } catch (Exception $e) { // 任意操作失败,回滚事务,撤销所有已执行的SQL $this->model->db->rollBack(); // 判断错误类型并返回 $errorType = str_contains($e->getMessage(), "access") ? "access" : "info"; echo json_encode(array("error", $errorType)); }
对Model类的要求
确保你的addUser方法满足以下两点:
- 执行SQL时不手动调用
commit(事务开启后PDO会自动禁用自动提交) - 操作失败时返回
false,或者直接抛出异常(如果已经抛出异常,上面的try-catch会自动捕获)
为什么手动删除方案不可靠?
之前的手动删除逻辑存在两个致命缺陷:
- 若
addUser("access")抛出SQL异常,代码会直接跳出当前逻辑块,根本走不到deleteUser的执行步骤 - 极端场景下(比如数据库连接突然中断),
deleteUser本身也可能执行失败,导致info表残留无效数据
事务是数据库层面的原子性保障,完全规避了这些风险。
内容的提问来源于stack exchange,提问作者Jalagui
相关产品推荐
相关产品推荐

