【问题标题】:Differences in behaviour of REQUIRES_NEW and NESTED propagation in Spring transactionsSpring 事务中 REQUIRES_NEW 和 NESTED 传播的行为差异
【发布时间】:2023-03-03 01:03:01
【问题描述】:

前言

首先:

它不是 Differences between requires_new and nested propagation in Spring transactions 的重复 - 我读过它,但我没有找到 我的问题

的答案

问题:

在阅读了我提到的主题后,我了解到传播级别之间在物理事务计数方面的主要区别:
2 db 事务 - 用于 REQUIRES_NEW 用于外部和内部方法
1 db 事务 - 用于 NESTED 用于外部和内部方法。如果底层数据库不支持保存点,它将无法工作

但是从我的角度来看,逻辑似乎是相同的。

如何理解在实践中使用哪个级别?有什么用例可以理解吗?行为差异的方便示例?

附言
我想由于不同的事务提交时间,其他事务差异有一些可见性。

附言 2

我也认为有性能差异:

@Transactional
public void outer(){
    for(int i=0;i<100500;i++){
        inner();
    }   
}

@Transactional
public void inner(){
   //some logic
}

对于这种情况,NESTED 会更好,因为 1 个长物理事务而不是 100500+1

【问题讨论】:

    标签: java spring transactions spring-transactions


    【解决方案1】:

    在您的示例中,如果 inner() 有:

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void inner(){
       //some logic
    }
    

    然后,如果它在 outer() 循环中的第二次调用中引发异常,则第一次调用的更改将已经提交 - 由其 REQUIRES_NEW

    如果inner() 有:

    @Transactional(propagation=Propagation.NESTED)
    public void inner(){
       //some logic
    }
    

    然后将回滚第一次调用的更改 - 因为outer() 中没有 catch 块。


    inner() 上的传播级别真正开始重要的是outer() 循环是否要处理inner() 中的异常:

    @Transactional
    public void outer() {
        for (int i = 0; i < 100500; i++) {
            try {
                inner();
            } catch (Exception ex) {
                // Report and continue
            }
        }
        // Something else that could fail
    }
    

    显然,REQUIRES_NEWNESTED 都只会保留来自成功的 inner() 调用的更改。但关键区别在于,对于 NESTED,如果 outer() 随后出现故障,仍然可以选择将其全部丢弃。

    正如您所说,另一个因素是可扩展性 - 某些数据库可能无法通过 NESTED 传播来理解父事务的大小。


    另外,这可能值得一说——尽管我怀疑这只是为了让示例更清晰。直接调用 this.inner() 会绕过 Spring 事务顾问。它需要被允许注入一个'advised bean'以允许@Transactional注释在调用之前和之后发挥它的魔力——例如。 nextAutowiredBean.inner().

    【讨论】:

    • 默认只有 RuntimeException 导致事务回滚
    • 我同意直接调用 inner() 不会起作用。我这样写是为了简化代码
    【解决方案2】:

    我看到的最大差异:

    嵌套情况下:

    • 如果外层事务被回滚,嵌套的事务也被回滚。
    • visibility:如果db做MVCC,同时很常见,
      • 嵌套的 tra 可以看到外部 tra 之前的变化。
      • 嵌套 tra 的更改将被提交,并且在外部提交后对其他 tra 可见。
    • 性能:请注意,外部事务的工作集被内部事务扩展。所以更多的锁,更多的 MVCC 原像存储,更长的重做日志条目。

    在 requires_new 的情况下:

    • 如果外层事务回滚,外层事务回滚时,内层事务的变化不会回滚。
    • 可见性:同时在 MVCC 很常见的情况下,
      • 内部 tra 不会看到尚未提交的外部 tra 所做的更改。
      • 在提交此内部 tra 之后,甚至在提交外部 tra 之前,嵌套 tra 的更改将立即被提交并且对其他 tra 可见。锁少了,但是由于提交多,外部操作多,redo-lock 中的记录多。

    性能的情况下,如果其他因素不重要,您可以在交易规模和交易数量之间找到一个平衡点。 i.m.O 如果嵌套比 requires_new 快,则该问题没有一般答案。

    【讨论】:

    • 嵌套不按要求做同样的事情吗?如果外部失败,内部也会在同一个物理事务中运行吗?
    • 不,requires_new 将在外部事务上完成内部事务缩进。 i.m.O.没有实物交易之类的东西,或者你的意思是什么。
    • 我没有requires_new,但需要。物理事务是实际的数据库连接。逻辑事务只是用事务注释注释的代码。这就是我从一些随机网站上理解的方式。
    • 必需的不是嵌套的。它与调用中使用的事务相同,因此如果发生回滚,调用者所做的更改也会被回滚。
    【解决方案3】:

    如果您的内部逻辑独立于外部逻辑,则使用 Requires_new,如果不使用嵌套。

    例如,您的外部方法可能正在处理包含大量记录的作业并调用保持作业状态(进度、警告和验证错误)的内部方法。您可能希望内部方法事务是独立的,并且它的数据库更改立即保存,以便系统的其他部分可以显示进度。如果外部方法遇到异常,它的事务会回滚,但内部方法的事务不会。

    当您需要将外部和内部更改同时保留或同时回滚时,您可能希望使用嵌套或依赖事务。例如,您需要创建一个新用户(使用“外部”服务)并保存他们的地址(使用“内部”服务)。如果您的要求是用户必须有一个地址,那么如果保存用户或地址失败,您希望两个更改都回滚。

    【讨论】:

    • 所以在嵌套模式下,事务只会在外部事务结束时提交,外部事务和内部事务都会提交更改,对吧?
    猜你喜欢
    • 2012-09-05
    • 2016-11-16
    • 2020-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 2012-09-25
    • 1970-01-01
    相关资源
    最近更新 更多