外部事务失败时回滚内部事务

Posted

技术标签:

【中文标题】外部事务失败时回滚内部事务【英文标题】:Rolling back inner transaction when outer transaction fails 【发布时间】:2014-04-11 10:14:23 【问题描述】:

我正在使用 ORMLite 访问 Java 中的 h2 数据库。 为了执行交易,我使用了静态方法TransactionManager.callInTransaction。

在单个独立事务的情况下,这可以正常工作。但是,如果事务相互嵌套,即使外部事务失败,内部事务也会被提交。

就像这段伪代码一样:

OuterDatabaseTransaction

  Loop
  
    InnerDatabaseTransaction
    
      //Multiple update or create statements
      //One of the InnerDatabaseTransactions throws a random exception
    
    //Alternatively the OuterDatabaseTransaction throws a random
    //exception but all commited InnerDatabaseTransactions should rollback still
  

所以我期望的是,如果任何内部事务失败,外部事务也会失败。如果外部事务失败,则不会提交任何内部事务。 目前,似乎每个内部事务都是单独提交的,并且例如不与外部事务共享相同的Savepoint。

更新(谢谢)

查看跟踪显示以下内容

[TRACE] JdbcDatabaseConnection connection is closed returned false
[TRACE] JdbcDatabaseConnection connection autoCommit is true
[TRACE] JdbcDatabaseConnection connection set autoCommit to false
[DEBUG] TransactionManager had to set auto-commit to false
[TRACE] JdbcDatabaseConnection save-point sp14: id=0 name=ORMLITE15 set with name ORMLITE15
[DEBUG] TransactionManager started savePoint transaction ORMLITE15
[TRACE] JdbcDatabaseConnection connection is closed returned false
[TRACE] JdbcDatabaseConnection connection autoCommit is false
[TRACE] JdbcDatabaseConnection save-point sp15: id=0 name=ORMLITE16 set with name ORMLITE16
[DEBUG] TransactionManager started savePoint transaction ORMLITE16
[TRACE] JdbcDatabaseConnection connection is closed returned false
[TRACE] JdbcDatabaseConnection update statement is prepared and executed returning 1: <...>
[DEBUG] BaseMappedStatement update data with statement <...> changed 1 rows
[TRACE] BaseMappedStatement update arguments: <...>
[TRACE] JdbcDatabaseConnection connection is committed for save-point ORMLITE16
[DEBUG] TransactionManager committed savePoint transaction ORMLITE16
-> [ERROR] TransactionManager after commit exception, rolling back to save-point also threw exception
[TRACE] JdbcDatabaseConnection connection set autoCommit to true
[DEBUG] TransactionManager restored auto-commit to true
[TRACE] JdbcDatabaseConnection connection is closed returned false

进入源代码显示,在 OuterDatabaseTransaction 回滚期间,异常会在以下函数内的 org.h2.engine.Session.java 中的 h2 源中引发。背后的原因,但我还不明白。

private void checkCommitRollback() 
  if (commitOrRollbackDisabled && locks.size() > 0) 
    throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED);
  

更新 2

发帖ORMLite Bug Report

【问题讨论】:

这可能是 ORMLite 中的错误。它可能在不知道有外部事务的情况下调用提交。小时。 这就是单步执行代码的样子。并不是说这是一个错误,而是似乎没有尝试合并交易。我想知道是否有任何方法可以在不过于明确的情况下实现这一点,因为内部和外部是在软件的不同组件中实现的。 外部和内部事务不需要共享相同的保存点即可按预期工作。 ORMLite 为外部和内部事务使用不同的保存点。如果需要忽略(使用try catch () )非关键失败的内部事务并在成功时提交其他事务,这可能会很有用。 我已在我的回答中附加了您遇到的异常的描述。 【参考方案1】:

要查找原因,您可以打开 trace 级别的登录。工作嵌套事务应该产生这样的日志:

TRACE: connection supports save points is true
TRACE: save-point <...> set with name ORMLITE1
DEBUG: started savePoint transaction ORMLITE1
...
TRACE: save-point <...> set with name ORMLITE2
DEBUG: started savePoint transaction ORMLITE2
...
TRACE: connection is committed for save-point ORMLITE2
...
TRACE: save-point <...> set with name ORMLITE3
DEBUG: started savePoint transaction ORMLITE3
...
TRACE: save-point ORMLITE3 is rolled back
...
TRACE: save-point ORMLITE1 is rolled back

在这个例子中,ORMLITE1 是外部事务的保存点,ORMLITE2 和 ORMLITE3 是内部事务的保存点。第一个内部事务最初提交,第二个事务回滚到 ORMLITE3 并导致外部事务回滚到 ORMLITE1,它又隐式取消了第一个内部事务。

但是如果你在日志中看到这个:

TRACE: connection supports save points is false

那么 JDBC 驱动程序不支持保存点并且嵌套事务将不起作用。理论上这不应该发生,因为 H2 状态支持保存点。

如果你看到这个:

ERROR: after commit exception, rolling back to save-point also threw exception

然后由于某种原因回滚到保存点失败。检查您对外部和内部事务使用相同的ConnectionSource

或者你我在日志中发现了导致问题的其他东西。此外,将日志附加到您的问题可能会有所帮助。我建议用真正的Java代码替换伪代码。

更新

这是您遇到的错误的官方描述:

COMMIT_ROLLBACK_NOT_ALLOWED = 90058

当尝试在触发器内调用提交或回滚时,或者在尝试调用隐式提交当前事务的触发器内的方法时,如果对象被锁定,则会引发代码 90058 的错误。这不是因为它会过早释放锁。

链接:http://www.h2database.com/javadoc/org/h2/constant/ErrorCode.html#c90058

这可能会帮助您进一步找到问题的原因。对我来说,很难说没有看到你的代码。祝你好运!

【讨论】:

感谢您到目前为止的解释。因为一旦我找到时间我将创建一个最小的项目来重现问题并将其发布到相应的错误跟踪器,这可能是一个错误。【参考方案2】:

你是对的, TransactionManager 在最后提交每个事务(内部事务和外部事务的行为相同),正如 Gray 在 cmets 中所写的那样。

问题出在 TransactionManager.java:170 中,即使在内部事务之后也会提交事务。 但是H2、mysql、Oracle数据库不支持带savepoint参数的sql COMMIT。

来自 Mysql 文档: "如果执行 COMMIT 或未命名保存点的 ROLLBACK,则删除当前事务的所有保存点。"

来自 Oracle 文档: “简单的回滚或提交会擦除所有保存点。当您回滚到某个保存点时,该保存点之后标记的所有保存点都将被擦除。您回滚到的保存点仍然存在。”

OrmLite 代码中也有对此的注释:JdbcDatabaseConnection.java:94

【讨论】:

ORMLite 忽略内部事务而只使用外部事务不是更好吗?在这种情况下,内部事务的更改至少会被回滚,而不是被提交,因为从外部事务的角度来看,提交它们会产生不一致的状态。 是的,这将是部分快速修复。正确实现唯一需要的是在 TransactionManager 中的内部事务的情况下必须省略 COMMIT。 但 TransactionManager.java 中的第 170 行仅在调用未引发异常时才提交,对吗?见:github.com/j256/ormlite-core/blob/master/src/main/java/com/j256/…

以上是关于外部事务失败时回滚内部事务的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data:重试时回滚事务

SpringBoot实现单元测试时回滚事务

SpringBoot实现单元测试时回滚事务

Spring Data:重试时回滚事务

Rails 3:将 ActiveRecord 操作和外部信用卡包装在单个事务中

如果外部事务范围未完成,内部事务范围会回滚吗?