Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django nested transactions - “with transaction.atomic()” -- Seeking Clarification

In Django nested transactions - “with transaction.atomic()” the question is, given this...

def functionA():
    with transaction.atomic():
        #save something
        functionB()

def functionB():
    with transaction.atomic():
        #save another thing

If functionB fails and rolls back, does functionA roll back too?

Kevin Christopher Henry replies with, "Yes, if an exception happens in either function they will both be rolled back." He then quotes the docs, which state:

atomic blocks can be nested. In this case, when an inner block completes successfully, its effects can still be rolled back if an exception is raised in the outer block at a later point.

This documentation quote doesn't seem to address the original question. The doc is saying that when the INNER BLOCK (which is functionB) completes successfully, its effects can still be rolled back if the OUTER block (which is functionA) raises an exception. But the question refers to the opposite scenario. The question asks, if the INNER block (functionB) FAILS, is the OUTER block (functionA) rolled back? This doc quote doesn't address that scenario.

However, further down in the doc we see this example...

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

...followed by this commentary...

In this example, even if generate_relationships() causes a database error by breaking an integrity constraint, you can execute queries in add_children(), and the changes from create_parent() are still there.

If I'm reading the doc correctly it's saying the call to generate_relationships() (which is analogous to the call to functionB in the original question) can FAIL and the changes made in create_parent() and add_children() will be committed to the database. This seems to contradict Kevin Christopher Henry's answer.

What's puzzling to me is that I see the same question/answer in Django nested Transaction.atomic.

I'm new to both Django and stackoverflow, so I don't have a lot of confidence in my reading of the doc, but it seems to contradict both of these responses. I'm looking for some clarification from someone more experienced. Thanks you so much.

like image 755
melodibit Avatar asked Feb 16 '18 21:02

melodibit


2 Answers

Here is some pseudo-code with nested transaction blocks and database operations X, Y, and Z:

with transaction.atomic():
    X
    with transaction.atomic():
        Y
    Z

If X raises an exception then obviously none of the operations will get the chance to commit in the first place.

If Y raises an exception—which was the question you referenced—then the outer block will also roll back. That doesn't have anything to do with nested transactions, per se, it happens because the Python exception bubbles up. The outer block will be exited by an exception, which always causes a rollback. That's true regardless of what caused the exception in the first place.

The non-obvious case is what happens when Z raises an exception, and that's why the documentation calls it out for special attention. As referenced, both X and Y will be rolled back:

When an inner block completes successfully, its effects can still be rolled back if an exception is raised in the outer block at a later point.

Now, it's also possible to catch the exception raised by the nested operation.

with transaction.atomic():
    X
    try:
        with transaction.atomic():
            Y
    except IntgrityError:
        pass
    Z

In this case, if Y throws an exception the inner block will be rolled back (because it exits with an exception) but the outer block won't (because it doesn't).

That doesn't contradict the information in either of the two answers you mentioned since those addressed specific questions (with code examples) that didn't involve catching any exceptions.

In any case, thanks for the feedback and the chance to give a more comprehensive answer.

like image 76
Kevin Christopher Henry Avatar answered Nov 17 '22 05:11

Kevin Christopher Henry


Second example is catching IntegrityError so outer transaction atomic wont be aware of the following error occurring

Wrapping atomic in a try/except block allows for natural handling of integrity errors

Which basically says if you want following block not to raise outer transaction rollback just try/catch integrity errors

As you already stated the following from documentation

atomic blocks can be nested. In this case, when an inner block completes successfully, its effects can still be rolled back if an exception is raised in the outer block at a later point.

So by default rollback will happen as error will propagate, and second example is a way to silence outer transaction rollback

like image 21
iklinac Avatar answered Nov 17 '22 06:11

iklinac