On 19 sept. 2013, at 16:52, Anssi Kääriäinen <[email protected]> wrote:
> After some investigation it turns out that this isn't about IntegrityErrors
> at all. Instead the problem is that inside @atomic block
> Model.save() uses @atomic(savepoint=False) internally. And
> @atomic(savepoint=False) forces the outer atomic block to roll back if errors
> happen.
Yes, that's the actual code path — which I introduced after you requested it
for performance reasons, in spite of my strong concerns that it could introduce
weird edge cases ;-)
Nonetheless, in my opinion, the root cause of the perceived misbehavior is that
user code (not Django code) catches IntegrityError inside an atomic block,
preventing __exit__ from detecting that a database exception has happened.
DatabaseErrors must be caught outside the atomic block, as documented.
Otherwise transaction.atomic will fail in various interesting and database
dependent ways. It's harder to diagnose on databases that don't fully enforce
transactional behavior ie. all but PostgreSQL.
> If I recall correctly there is transaction.set_rollback(False) which can be
> used to remove forced rollback.
This is a private API for a reason. Someone deeply familiar with the
implementation details of transaction management in Django 1.6 and with an
esoteric transaction management system might find this private API useful but
it's very obviously not the answer to the original question. Please, everyone,
don't do this.
> In general, there are three ways to respond to errors inside transactions:
> 1. allow usage of the TX (MySQL, SQLite etc), allow user to decide
> commit/rollback
> 2. forbid usage of the TX (PostgreSQL), force it to be rolled back.
> 3. allow usage of the TX, but force rollback (Django)
You're comparing apples to oranges here. If Django is backed by PostgreSQL, how
can Django allow using the transaction if PostgreSQL forbids it?
> To me it seems explicit error when using connection in "forced to rollback"
> state is better than allowing saving more data, then silently rolling back
> the transaction. As you said this is useless.
Django doesn't allow saving more data in a broken transaction. If a
DatabaseError occurs, execution jumps straight to __exit__ which sees an
exception and triggers a rollback.
Of course, you can disregard the documentation and break this expectation by
catching the exception. But there's no way around this in Python. A context
manager cannot prevent catching exceptions.
> To be clear, this is about exceptions (database or other) in
> atomic(savepoint=False) blocks, not about caught IntegrityErrors - Django
> will allow you to continue and commit the TX in the caught IntgerityError
> case assuming the DB allows that.
I designed this part of Django to forbid continuing and committing the
transaction. The reason is cross-database compatibility. I had to enforce the
behavior of the most restrictive database, which is also the most correct,
namely PostgreSQL.
Sure, you can do virtually anything by disregarding the docs and abusing
private APIs, but I wouldn't describe that as "Django will allow you to…"
If I still haven't convinced you that I know what I'm talking about, here's an
example with the exact same problem but without atomic(savepoint=False).
with transaction.atomic:
try:
do_stuff_in_db() # raises DatabaseError
except DatabaseError:
pass
do_more_stuff_in_db() # works only on lax databases eg. MySQL
In this example, it's totally possible that the transaction.atomic block will
end up with a rollback even if do_more_stuff_in_db() succeed. Why? Because the
commit is likely to fail if a statement failed during the transaction, and that
will result in an implicit rollback!
At the highest level, my design is based on avoiding implicit rollbacks because
explicit is better than implicit. But you can still force one if you try hard
enough.
--
Aymeric.
--
You received this message because you are subscribed to the Google Groups
"Django developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/django-developers.
For more options, visit https://groups.google.com/groups/opt_out.