如何回滚 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,你可以在一个函数内去掉异常块,如果发生错误,其中的事务运行会自动回滚。从我有限的测试来看,情况似乎是这样。
我正在为 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,你可以在一个函数内去掉异常块,如果发生错误,其中的事务运行会自动回滚。从我有限的测试来看,情况似乎是这样。