You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:03:13