Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences in behaviour of REQUIRES_NEW and NESTED propagation in Spring transactions

Preface

First of all:

It is not duplicate of Differences between requires_new and nested propagation in Spring transactions - I read it but I didn't found answer to my question

Question:

After reading topic I mentioned I understood that main difference between propagation levels in count of physical transactions:
2 db transactions- for REQUIRES_NEW for outer and for inner method
1 db transaction - for NESTED for outer and for inner method. It will not work if underlying database doesn't support savepoints

But looks like logic will be the same from my point if view.

How to understand which level to use in practise? Any use cases to understand it? Handy examples of behavioural differences?

P.S.
I suppose there are some visibility for other transactions differences because different transaction commit time.

P.S.2

Also I suppose there are performance difference:

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

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

For that case NESTED will be better because of 1 long physical transaction instead of 100500+1

like image 682
gstackoverflow Avatar asked Oct 21 '18 18:10

gstackoverflow


People also ask

How does the transaction propagation setting impact the behavior of transactions?

Transaction Propagation - MANDATORYIf none exists then gets executed with out transaction. Always executes in a transaction. If there is any existing transaction it is used. If there is no existing transaction it will throw an exception.

What is the difference between required and Requires_new?

REQUIRED, spring checks for any existing transaction. If yes, it uses that old transaction otherwise it creates a new one. But, the disadvantage of Propagation. REQUIRES_NEW is that even if the inner method fails to execute (because of some exception), the outer method commits the transaction.

What is the transaction behavior of the Propagation_requires_new mode?

What is the transaction behavior of the PROPAGATION_REQUIRES_NEW mode? Select a unique answer. If a transaction exists, the current method should run within this transaction. Otherwise, it should start a new transaction and run within its own transaction.


3 Answers

As it stands in your example, if inner() had:

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

Then if it threw an exception from the second call in the outer() loop, the changes from the first call would have already been committed - by its REQUIRES_NEW.

If inner() had:

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

Then the changes from the first call would be rolled back - because there is no catch block in outer().


The point where the propagation level on inner() really starts to matter is if the outer() loop is going to handle exceptions in 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
}

Clearly both REQUIRES_NEW and NESTED would only keep changes from the successful inner() calls. The key difference though is that with NESTED, there is still the option to throw it all away if there is a subsequent failure in outer().

As you say, the other factor is scalability - some databases may not appreciate the size of the parent transaction with NESTED propagation.


Also, it might be worth saying - though I suspect it was just aiming for clarity in the example. Calling this.inner() directly is bypassing the Spring transaction advisor. It needs to be allowed to inject an 'advised bean' to allow the @Transactional annotation to do its magic before and after the call - e.g. nextAutowiredBean.inner().

like image 198
df778899 Avatar answered Sep 19 '22 23:09

df778899


The big differences I see:

in case of nested:

  • If the outer transaction is rolled back,the nested tra is rolled back as well.
  • visibility: if the db does MVCC that is quite common meanwhile,
    • the nested tra sees the previous changes of the outer tra.
    • changes of the nested tra will be seen committed and visible to other tras after commit of the outer.
  • performance: be aware, that the working set of the outer transactions gets expanded by the inner transactions. So more locks, more preimage-storage for MVCC, a longer redo-log-entry.

In case of requires_new:

  • If the outer transaction is rolled back, the changes of the inner tra will not be rolled back in case of rolback of the outer tra.
  • visibility: in case of MVCC that is quite common meanwhile,
    • the inner tra will not see changes done by the yet uncommitted outer tra.
    • changes of the nested tra will be seen committed and visible to other tras immediately after commit of this inner tra, even before the commit of the outer tra. Less locks, but because of the many commits more external operations, more records in the redo-lock.

In case of performance, if the other factors are not important, you can find a break even between transaction size and transaction number. i.m.O there is no general answer to the question, if nested is faster than requires_new.

like image 31
aschoerk Avatar answered Sep 22 '22 23:09

aschoerk


If your inner logic is independent of the outer logic then use Requires_new, if not use nested.

For example, your outer method may be processing a job containing a large number of records and calling an inner method that persists job status (progress, warnings and validation errors). You would want the inner methods transaction to be independent, and its db changes to be saved immediately so some other part of the system could display the progress. If the outer method encounters an exception, it's transaction would rollback, but the inner method's transactions would not.

You would want to uses nested or dependent transactions when you need the outer and inner changes to either both be persisted or both be rolled back together. For example, you need to create a new user (using the "outer" service) and save their address (using the "inner" service). If your requirements are a user must have an address, then if the saving the user or address fails you want both changes to be rolled back.

like image 33
Jean Marois Avatar answered Sep 22 '22 23:09

Jean Marois