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 inadd_children()
, and the changes fromcreate_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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With