如何在 PostgreSQL 中发生错误时回滚事务?

Mar*_*llo 9 postgresql transactions exception plpgsql rollback

我正在为 PostgreSQL 编写一个脚本,因为我希望它以原子方式执行,所以我将它包装在一个事务中。
我希望脚本看起来像这样:

BEGIN
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
END; -- A.k.a. COMMIT;
Run Code Online (Sandbox Code Playgroud)

但是,在这种情况下,pgAdmin 在初始BEGIN. 如果我通过像这样附加分号来终止命令:BEGIN;它会通知我附近的错误EXCEPTION
我意识到我可能混淆了控制结构和事务的语法,但是我在文档中找不到任何关于如何回滚失败事务的内容(也没有在 SO 中)。

我还考虑过事务可能会在出错时自动回滚,但由于以下脚本,情况似乎并非如此:

BEGIN;
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
COMMIT;
Run Code Online (Sandbox Code Playgroud)

警告我:ERROR: current transaction is aborted, commands ignored until end of transaction block然后我必须手动ROLLBACK;进行交易。

似乎我在这里遗漏了一些基本的东西,但是什么?

编辑:
我也试过DO像这样使用:

DO $$
BEGIN
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
END; $$
Run Code Online (Sandbox Code Playgroud)

pgAdmin 回击我:ERROR: cannot begin/end transactions in PL/pgSQL. HINT: Use a BEGIN block with an EXCEPTION clause instead.这让我困惑不已,因为这正是我(我认为)正在做的事情。

接受后编辑: 关于 Laurenz 的评论:“您的 SQL 脚本将包含一个 COMMIT。这将结束事务并将其回滚。” - 这不是我观察到的行为。请考虑以下示例(这只是我在原始问题中已经提供的示例的具体版本):

BEGIN;

-- Just a simple, self-referencing table.
CREATE TABLE "Dummy" (
    "Id" INT GENERATED ALWAYS AS IDENTITY,
    "ParentId" INT NULL,
    CONSTRAINT "PK_Dummy" PRIMARY KEY ("Id"),
    CONSTRAINT "FK_Dummy_Dummy" FOREIGN KEY ("ParentId") REFERENCES "Dummy" ("Id")
);

-- Foreign key violation terminates the transaction.
INSERT INTO "Dummy" ("ParentId")
VALUES (99);

COMMIT;
Run Code Online (Sandbox Code Playgroud)

当我执行上面的脚本时,我得到了:ERROR: insert or update on table "Dummy" violates foreign key constraint "FK_Dummy_Dummy". DETAIL: Key (ParentId)=(99) is not present in table "Dummy".正如预期的那样。但是,如果我然后尝试检查我的Dummy表是否已创建或回滚,如下所示:

SELECT EXISTS (
    SELECT FROM information_schema."tables"
    WHERE "table_name" = 'Dummy');
Run Code Online (Sandbox Code Playgroud)

而不是一个简单的false,我得到了我已经提到过两次的相同错误:ERROR: current transaction is aborted, commands ignored until end of transaction block。然后我必须通过发布手动终止交易ROLLBACK;

所以对我来说,上面提到的评论似乎是错误的,或者至少我在这里严重误解了一些东西。

Lau*_*lbe 5

您不能ROLLBACK在 PL/pgSQL 中使用,除非在过程内部的某些有限情况下。

您不需要在 PL/pgSQL 代码中显式回滚。只要让异常从 PL/pgSQL 代码中传播出去,就会导致错误,从而导致整个事务回滚。

您的评论表明此代码是从 SQL 脚本调用的。那么解决方案是COMMIT在 PL/pgSQL 代码之后的某个地方有一个SQL 脚本。这将结束事务并将其回滚。

  • 好的,但如果是这种情况,那么为什么我(我在问题中提到了这一点)得到:“错误:当前事务被中止,命令被忽略直到事务块结束”并且必须手动“回滚”事务能够执行任何进一步的命令吗? (4认同)
  • 您无法在 PL/pgSQL 代码中结束事务。您的 PL/pgSQL 代码是从某处调用的。这就是您需要执行“ROLLBACK”的地方。 (2认同)