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

时间:2015-01-11 11:01:42

标签: c++ postgresql transactions libpq

我正在研究一些利用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调用启动的事务,因此update语句将随后不包含在事务块的原子性中。

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

  

当已经在事务块内时发出BEGIN将引发警告消息。交易状态不受影响。要在事务块中嵌套事务,请使用保存点(请参阅SAVEPOINT)。

关于保存点的评论似乎有点误导我; PostgreSQL不支持嵌套(自治)事务;保存点仅提供回滚到现有事务中间点的方法。保存点本身不是交易,因此我无法用doFoo()替换它们中的BEGIN和END,因为这样我就无法单独调用doFoo()(意思不是来自doFooPlus())并且仍然获得doFoo()执行的插入和删除的事务原子性。

关于嵌套BEGIN“不受[影响”]事务状态的评论似乎暗示PostgreSQL不会计算它,实际上完全会忽略它,但引用的短语不是相当让我明白我不会在Stack Overflow上问这个问题。我仍然抱着一丝希望PostgreSQL仍然会将嵌套的BEGIN计入某种内部“嵌套级别”,这将被第一个END递减,然后再次递减第二个END,导致整个序列语句被视为一个原子事务。

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

编辑:对于任何有兴趣的人,我今天意识到我可以相当轻松地自己测试这个问题,只需编写一个类似于上面的示例代码尝试做的事情的程序,然后检查它对数据库的影响。

我不会详细介绍程序的内部细节,但是下面的命令基本上运行create table t1 (a int, b int, c int ); insert into t1 (a,b,c) values (0,0,0);,然后按顺序运行每个给定的SQL语句,最后输出结果表数据,因此,您可以看到第二个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语句将导致先前的语句被回滚(即使你还得到一条“注意:消息中没有正在进行的事务”消息)窗格...),除了它仍然以某种方式尊重批处理中的begin ... end块(忽略上面所示的嵌套begin语句),这看起来非常类似于自治事务,postgres 支持,因为我相信我们已经在这个线程中建立了。因此,例如,如果您直接在pgAdmin III中运行上述7个语句,那么您最终获得的数据为1|1|0

但无论是无意识的混淆,无可争议的结论是postgres 计算begin ... end块的嵌套级别,所以你必须注意只有将自己置于一个顶级begin ... end区块内。

1 个答案:

答案 0 :(得分:2)

正如文档所说,第二个BEGIN不会影响事务状态。这意味着它不能导致忽略以下COMMIT。因此,第一个COMMIT确实会提交交易,之后的所有内容都会按照您的描述进行。

最简单的解决方案是将事务控制的责任转移给调用者。将您的BEGIN / COMMIT 置于<{1}}之外的,您不再需要担心哪个子例程负责启动交易。更简单的是,将doFooPlus()foo()实现为服务器端函数 - 这些函数本质上是原子的 - 并完全忘记客户端事务控制。

也就是说,您的示例中的特殊事务管理模式可能很有用。最终,实现这一点只需要在fooPlus()指针旁边传递一些额外的信息,并在每个PGConn / BEGIN / COMMIT周围进行检查。

当然,整个模式有点笨拙;如果你需要做多次,你需要一些东西来封装连接,事务控制函数和状态(一个简单的深度计数器可能会这样做)。但在开始编写自己的API之前,我先来看看那些已经存在的API。