PostgreSQL函数是否具有事务性?

97

像以下这样的 PostgreSQL 函数是否自动为事务处理?

CREATE OR REPLACE FUNCTION refresh_materialized_view(name)
  RETURNS integer AS
$BODY$
 DECLARE
     _table_name ALIAS FOR $1;
     _entry materialized_views%ROWTYPE;
     _result INT;
 BEGIN          

     EXECUTE 'TRUNCATE TABLE ' || _table_name;

     UPDATE materialized_views
     SET    last_refresh = CURRENT_TIMESTAMP
     WHERE  table_name = _table_name;

     RETURN 1;
END
$BODY$
  LANGUAGE plpgsql VOLATILE SECURITY DEFINER;


换句话说,如果在函数执行过程中发生错误,那么任何更改都将被回滚吗? 如果这不是默认行为,我该如何使函数具有事务性


10
@FrankHeikens 我的问题是“如果出现错误,更改是否会自动回滚”,而不是“如果我执行ROLLBACK,更改是否会回滚”。 - Dónal
2
@Don 请注意,TRUNCATE具有或曾经具有一些不太正常的事务行为。我不记得具体细节,请搜索pgsql-general档案。 - Craig Ringer
1
是的,函数是事务性的,即使是用 LANGUAGE SQL 编写的。 - ma11hew28
据我所知,TRUNCATE 忽略所有保存点并直接彻底破坏表数据。 - G_V
6个回答

105

PostgreSQL 12 更新现在有限支持顶层的 PROCEDURE 来进行事务控制。除非使用了新的顶层过程,否则您仍然不能在常规SQL可调用函数中管理事务, 因此下面的内容仍然有效。


函数是其所在事务的一部分。如果事务回滚,则它们的影响也会回滚。如果事务提交,则它们的工作也会提交。函数内的任何BEGIN ... EXCEPT块都像(并且在内部使用)保存点,就像SAVEPOINTROLLBACK TO SAVEPOINT SQL语句一样。

函数要么完全成功,要么完全失败,除非使用BEGIN ... EXCEPT错误处理。如果函数内部引发错误并且未被处理,则调用该函数的事务将中止。中止的事务无法提交,如果它们试图提交,COMMIT将被视为ROLLBACK,与任何其他错误的事务一样。请看以下示例:

regress=# BEGIN;
BEGIN
regress=# SELECT 1/0;
ERROR:  division by zero
regress=# COMMIT;
ROLLBACK

看看因为零除而处于错误状态的事务如何在COMMIT上回滚?

如果您调用一个没有明确周围事务的函数,那么规则与任何其他 Pg 语句完全相同:

BEGIN;
SELECT refresh_materialized_view(name);
COMMIT;

(其中,如果 SELECT 引发错误,则 COMMIT 将失败)。

PostgreSQL 目前不支持函数中的自主事务(autonomous transactions),即过程/函数可以独立于调用事务进行提交/回滚。但是使用通过 dblink 创建的新会话可以模拟此功能。

但是,在 PostgreSQL 中存在非事务性行为或不完美的事务性行为。如果在标准的 BEGIN; do stuff; COMMIT; 块中存在非事务性行为,则在函数中也存在非事务性行为,例如 nextvalsetvalTRUNCATE 等。


2
非常清晰的解释。特别感谢您用简短而直接的例子阐述您的答案。 - informatik01
我相信自从PG11以来,您现在可以在过程内执行实际提交了。https://blog.dbi-services.com/postgresql-11-procedures-and-transaction-control/ - gertvdijk
(更多信息请参见官方文档:https://www.postgresql.org/docs/12/plpgsql-transactions.html) - gertvdijk
@craig-ringer,您所说的“有限”存储过程支持是什么意思?为什么会受到限制? - EngineerSpock

43

鉴于我的PostgreSQL知识不如Craig Ringer深入,我将尝试给出一个更简短的回答:是的。

如果您执行的函数中存在错误,那么这些步骤都不会对数据库产生影响。

同样地,如果您在PgAdmin中执行查询,情况也是一样。

例如,如果您在查询中执行以下操作:

update your_table yt set column1 = 10 where yt.id=20;

select anything_that_do_not_exists;

在数据库中,不会保存在行 id = 20your_table 中的更新。 2018年9月更新 为了澄清概念,我做了一个使用非事务函数 nextval 的小例子。
首先,让我们创建一个序列: create sequence test_sequence start 100; 然后,让我们执行: update your_table yt set column1 = 10 where yt.id=20; select nextval('test_sequence'); select anything_that_do_not_exists; 现在,如果我们打开另一个查询并执行 select nextval('test_sequence'); 我们将得到101,因为第一个值(100)在后面的查询中被使用了(这是因为序列不是事务性的),尽管更新没有提交。

6
感谢您清晰明了的回答!在阅读克雷格的回答后,我感到不确定。 - stone
2
比克雷格的回答好多了。 - Nulik
1
要注意非事务性的东西,比如nextval;如果它们不是像普通查询一样完全事务性的,那么在函数中也不是。 - Craig Ringer

12

https://www.postgresql.org/docs/current/static/plpgsql-structure.html

在 PL/pgSQL 中,使用 BEGIN/END 来分组语句与 SQL 中的具有类似名称的事务控制命令不应混淆。PL/pgSQL 的 BEGIN/END 仅用于分组,它们并不启动或结束一个事务。函数和触发器过程总是在由外部查询建立的事务中执行 - 它们无法启动或提交该事务,因为它们没有执行上下文。然而,包含 EXCEPTION 子句的块有效地形成了子事务,可以回滚而不影响外部事务。有关更多信息,请参阅第 39.6.6 节。


7
在函数级别上,它不是事务性的。换句话说,函数中的每个语句都属于单个事务,这是默认的数据库自动提交值。自动提交默认为true。但无论如何,您必须使用以下方式调用函数: select schemaName.functionName() 以上语句“select schemaName.functionName()”是一个单独的事务,我们将其命名为T1,因此函数中的所有语句都属于T1事务。以这种方式,函数是在一个单独的事务中的。

这完全不是真的。函数总是原子的。 - undefined

6

Postgres 14更新:在存储过程/函数的BEGINEND块之间编写的所有语句都将在单个事务中执行。因此,如果在执行此块时出现任何错误,将自动回滚事务。


对于PostgreSQL 15:“默认情况下,PL/pgSQL函数中发生的任何错误都会中止函数和周围事务的执行。” 参见43.6.8. 捕获错误 - Ludovic Kuty

0
此外,原子事务还包括触发器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接