如何回滚 PostgreSQL 中的错误事务?

How to roll back a transaction on error in PostgreSQL?

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

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

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

我还考虑过,也许事务会在错误时自动回滚,但似乎不是这样,因为以下脚本:

BEGIN;
-- 1) Execute some valid actions;
-- 2) Execute some action that causes an error.
COMMIT;

警告我: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; $$

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

POST-接受编辑: 关于 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;

当我执行上面的脚本时,我收到了: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 table 是像这样创建还是回滚:

SELECT EXISTS (
    SELECT FROM information_schema."tables"
    WHERE "table_name" = 'Dummy');

而不是简单的 false,我得到了我已经提到过两次的相同错误:ERROR: current transaction is aborted, commands ignored until end of transaction block。然后我必须通过发出 ROLLBACK;.

手动终止交易

所以在我看来,要么上面提到的评论是错误的,要么至少我严重误解了这里的某些内容。

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

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

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

我认为您一定是在使用旧版本,因为您问题中的确切代码对我来说没有错误:

(以上是 PostgreSQL 13.1 和 pgAdmin 4.28。)

它对我来说也很好,没有异常块:

按照this comment,你可以在一个函数内去掉异常块,如果发生错误,其中的事务运行会自动回滚。从我有限的测试来看,情况似乎是这样。