PostgreSQL 是否计算嵌套的 BEGIN 和 END 语句,即使它不支持自主事务?

Does PostgreSQL count nested BEGIN and END statements, even though it doesn't support autonomous transactions?

我正在编写一些使用 libpq 与 PostgreSQL 数据库交互的 C++ 代码,并且我已经开始编写一些函数,每个函数都在内部启动一个事务,应用一些更改到数据库,然后结束事务。

我现在想结合另一个 DML 语句调用其中一个函数,所有这些都在同一个事务中执行。像这样(注意:非常简单):

void doFoo(PGconn* con) {
    PQexec(con, "BEGIN" );
    PQexec(con, "insert into ..." );
    PQexec(con, "delete from ..." );
    PQexec(con, "END" );
}

void doFooPlus(PGconn* con) {
    PQexec(con, "BEGIN" );
    doFoo(con);
    PQexec(con, "update ..." );
    PQexec(con, "END" );
}

void main(void) {
    doFooPlus(con);
}

但是,根据我所阅读的所有内容,PostgreSQL 似乎不支持这种事务嵌套。我想明确一点:我不需要自主事务,我知道 PostgreSQL 不支持,而且我不需要对嵌套(或其他)BEGIN 语句的任何显式回滚功能,这可能是通过保存点完成,显然不是上面的代码在任何时候试图做的。我只是想从代码的结构上确认一下上面的代码是否会做人们希望它做的事情。

让我尝试进一步澄清。这是 PostgreSQL 从上面的 C++(实际上只是直接的 C)代码中看到的结果:

BEGIN
BEGIN
insert into ...
delete from ...
END
update ...
END

我担心的是第二个 BEGIN 调用被 完全 忽略,因此第一个 END 调用将结束由第一个 BEGIN 调用启动的事务,等等更新语句将不会包含在事务块的原子性中。

根据http://www.postgresql.org/docs/9.4/static/sql-begin.html

Issuing BEGIN when already inside a transaction block will provoke a warning message. The state of the transaction is not affected. To nest transactions within a transaction block, use savepoints (see SAVEPOINT).

关于保存点的评论似乎有点误导我; PostgreSQL 不支持嵌套(自治)事务;保存点仅提供一种回滚到现有事务中间点的方法。保存点本身不是事务,所以我不能用它们替换 doFoo() 中的 BEGIN 和 END,因为那样我就不能自己调用​​ doFoo()(意思不是来自 doFooPlus())并且仍然获得由 doFoo().

执行的插入和删除的事务原子性

嵌套 BEGIN 关于事务状态 "not [being] affected" 的注释似乎暗示 PostgreSQL 不会计算它,实际上会完全忽略它,但是引用的短语相当 没有让我清楚地知道我不会在 Stack Overflow 上问这个问题。我仍然抱有一丝希望,PostgreSQL 仍会将嵌套的 BEGIN 计入某种内部 "nesting level",它将在第一个 END 时递减,然后在第二个 END 时再次递减END,导致整个语句序列被视为一个原子事务。

所以,有人可以确认 PostgreSQL 是否这样做吗?如果没有,您能否提供有关如何在我的代码中最好地解决此问题的建议?我正在考虑添加一个 bool 参数以允许 doFoo() 的调用者指定是否创建交易,doFooPlus() 可以传递 false.

编辑: 对于任何感兴趣的人,我今天意识到我可以很容易地自己测试这个问题,只需编写一个程序来执行与上述示例代码类似的操作尝试做,然后检查它对数据库的影响。

我不会详细介绍程序的内部结构,但下面的命令基本上是 运行s create table t1 (a int, b int, c int ); insert into t1 (a,b,c) values (0,0,0);,然后 运行s 每个给定的 SQL顺序语句,最后打印结果table数据,所以可以看到第二个begin和最后的rollback被完全忽略了:

> pgtestabc begin begin 'update t1 set a=1;' 'update t1 set b=1;' end 'update t1 set c=1;' rollback;
executing "begin"...done
executing "begin"...done
executing "update t1 set a=1;"...done
executing "update t1 set b=1;"...done
executing "end"...done
executing "update t1 set c=1;"...done
executing "rollback"...done
1|1|1

另请注意,您无法通过简单地从 GUI 客户端(例如 pgAdmin III)批处理 运行 宁 SQL 语句来完成此精确测试。那个特定的客户似乎对交易有一些魔力;它似乎将批处理包含在隐式事务中,因此 rollback 语句将导致先前的语句回滚(即使您在“消息”窗格中也收到 "NOTICE: there is no transaction in progress" 消息...) , 除了它在批处理中仍然以某种方式尊重 begin...end 块(忽略上面演示的嵌套 begin 语句),这令人困惑地看起来非常类似于 postgres 做的自治事务 支持,因为我相信我们已经在此线程中建立了。因此,例如,如果您直接在 pgAdmin III 中 运行 上面的 7 个语句,您最终得到的数据是 1|1|0.

但是不管那个无意的混淆,无可辩驳的结论是 postgres 计算 begin...end 块的嵌套级别,所以你必须注意只将自己放在一个顶级 begin...end 块中。

如文档所述,第二个 BEGIN 不会影响事务状态。这意味着它不会导致以下 COMMIT 被忽略。所以第一个 COMMIT 确实会提交交易,之后的一切都会按照你描述的那样进行。

最简单的解决方案是将事务控制的责任转移给调用者。把你的BEGIN/COMMIT放在调用doFooPlus()之外,你再也不用担心哪个子程序负责发起交易了.更简单的是,将 foo()fooPlus() 作为服务器端函数实现——它们本质上是原子的——并且完全忘记客户端事务控制。

也就是说,您示例中的那种临时事务管理模式可能很有用。最终,实现这个只需要在 PGConn 指针旁边传递一些额外的信息,并在每个 BEGIN / COMMIT / ROLLBACK.

周围检查它

当然,整个模式有点笨拙;如果您需要多次执行此操作,则需要一些东西来封装连接、事务控制函数和状态(一个简单的深度计数器可能就可以做到)。但在着手编写您自己的 API 之前,我会先看一下已有的内容。