批量更新:connection.commit() 在最后,使用 setAutoCommit(false),但数据不会回滚

Batch Update: connection.commit() at the very end, with setAutoCommit(false), but data doesn't get rolled back

我的 Java JDBC 批量更新请求结构为

conn.setAutoCommit(false);

for (int i = 0; i < items.size(); i++) {
    //...
    ps.addBatch();
}

ps.executeBatch();

// Suppose an exception happens right before a Commit
if (someCondition)
   throw new Exception("test");

conn.commit(); // Commit at the very end

我的理解是,当发生该异常时,我从未达到 commit。所以我的数据不应该被保留,对吧?只要commit在最后,并且指定setAutoCommit(false);,任何异常都不需要回滚?

但我发现数据 确实 得到了持久化。我的问题是为什么?我需要 connection.rollback(); 吗? (这是一个 Postgres 数据库)

请看一下PostgreSQL的事务隔离级别:

https://www.postgresql.org/docs/9.5/transaction-iso.html

对你有好处的default is READ COMMITTED

回读 'uncommited' 更改取决于您使用的 JDBC 连接。根据事务隔离级别,每个连接都会有不同的 'view' 数据。

设置时的最佳实践

conn.setAutoCommit(false);

是在成功时提交并在 catch 子句中回滚:

conn.setAutoCommit(false);
try {
  ...
  ps.executeBatch();
  conn.commit();
} catch (Exception e) {
  conn.rollback();
}

既不调用提交也不调用回滚将使连接保持打开状态,从而增加数据库的负载以维护未提交的更改。然后 JVM 将关闭连接,数据库将回滚所有数据。

JEE 容器在发生未捕获的异常时自动回滚事务。

每个事务都需要提交或回滚才能结束。如果您禁用了自动提交并且不调用 connection.commit(),则它不会提交。

any exceptions don't require a rollback

So my data shouldn't be persisted, right?

数据以某种方式持久化,是的。活动事务保持其数据隔离,因此同一连接上 运行 的语句将看到更改,但其他连接在调用提交之前不会看到更改。

Postgres 使用多版本并发控制,这意味着插入、更新和删除实质上会创建行的“版本”,并且 Postgres 在内存中跟踪哪些事务可以看到哪些行的哪些版本。

那么,如果您的事务既不提交也不回滚会怎样?

在事务提交或回滚之前,锁将锁定在事务中更改的行上,这将阻止将来对相同数据的更改。这很容易导致中断。

始终提交或回滚您的事务。永远不要让它们悬空。

如果您从不提交或回滚事务会怎样?

我不太确定 Postgres 或您的应用程序中发生了什么。许多网络都有空闲套接字超时,因此最终您的连接将终止并触发回滚。 Postgres 也可以有自己的空闲事务超时(不确定 - 有根据的猜测)。但永远不要依赖这些东西。你无法控制伤害。

始终提交或回滚您的事务。永远不要让它们悬空。

这对您的代码意味着什么?

当您的异常被抛出时,调用堆栈将展开,跳过提交。所以你的交易不会提交。根据捕获异常的位置,conn 对象可能在也可能不在范围内。在范围内捕获它,以便您可以回滚。为什么?因为你应该:

始终提交或回滚您的事务。永远不要让它们悬空。