Hi Tore,

I’ve been meaning to check whether your use case was supported, because it 
should be. I’m sorry, it took me a few days to find the time to investigate.

While I was testing, I produced an exception which looks similar to the problem 
you originally reported: https://code.djangoproject.com/ticket/26340.

Specifically, could you try adding `self.needs_rollback = False` at the bottom 
of the `BaseDatabaseWrapper.rollback()` and tell me if that helps?

(Since I don’t remember hearing you mention savepoints, you can probably ignore 
everything I say about savepoints.)



> On 07 Mar 2016, at 12:55, Tore Lundqvist <t...@mima.x.se> wrote:
> 
> an example of when it happens is when:
> 
> Starting with auto commit on.
> transaction.set_autocommit(False)
> ... a lot of time passes without the connection being used so it times out....
> close_old_connections()

There’s a long discussion of this use case here: 
https://code.djangoproject.com/ticket/21597.

> Now autocommit is on again. I'm not saying that it's a bug, but it's 
> inconvenient.

This surprises me. The connection shouldn’t reopen at all. It should become 
unusable, for the reasons explained in that issue (ticket 21597).

Regardless, at any point in the code where you expect not to use the database 
connection for some time, you should close it explicitly with 
connection.close(). Then you can call set_autocommit(False) when you’re about 
to start using the database connection again.

Since I don’t expect the database timeout to be reached while handling a HTTP 
request, I assume this happens in a long-running management command. You could 
have another settings module just for the purpose of running that command. It 
would be the same as your regular settings module, except it would set the 
AUTOCOMMIT option to False in the database configuration. In that case, you 
wouldn’t need to call set_autocommit(False).



> The problem is that the legacy code uses COMMIT side effects to manage disk 
> writes and avoid deadlocks, securing an adequate transactional integrity is 
> often not an issue.

That’s an interesting way to use transactions, and to be honest, one I never 
imagined ;-)

While I wouldn’t recommend that design for new code, Django shouldn’t prevent 
you from continuing to use it.

Obviously, some other users of Django need transactional integrity. We can’t 
relax that constraint when discussing changes to Django.

> So I'm trying to disable or at least use as little as possible of Djangos 
> transaction management for this code.


After transaction.set_autocommit(False), transaction.commit() and 
transaction.rollback() seem to work fine.

As I pointed out in my previous messages, you have to rollback with 
transaction.atomic() when you’re catching IntegrityError.

I thought you could rollback with transaction.rollback() or 
transaction.savepoint_rollback(). It turns out you also need to call 
transaction.set_rollback(False). Essentially this tells Django “hey, I’m happy 
to continue from this point, just go ahead”.

I think Django could do better. That’s why I filed the issue I mentioned at the 
beginning of this email (ticket 26340).

Given MySQL’s interesting approach to transactional integrity, you can call 
transaction.set_rollback(False) after a query that failed with an 
IntegrityError and keep going.



In case that’s useful, here are some patterns that work currently. A and B are 
two distinct sessions. I’m using session B to check whether the changes from 
session A have been committed.

I have a user model with a unique email field. I used PostgreSQL because I 
don’t have a MySQL instance around. If you’re getting different results on 
MySQL, it’s a bug.

A >>> from django.db import IntegrityError, transaction
A >>> transaction.set_autocommit(False)
A >>> User.objects.create(email='us...@example.com')
A <User: us...@example.com>
A >>> User.objects.create(email='us...@example.com')
A <User: us...@example.com>

B                 >>> User.objects.filter(email__startswith='user')
B                 []

A >>> transaction.commit()

B                 >>> User.objects.filter(email__startswith='user')
B                 [<User: us...@example.com>, <User: us...@example.com>]

A >>> try:
A ...     with transaction.atomic():
A ...         User.objects.create(email='us...@example.com')
A ... except IntegrityError:
A ...     print("duplicate")
A ... 
A <User: us...@example.com>
A >>> try:
A ...     with transaction.atomic():
A ...         User.objects.create(email='us...@example.com')
A ... except IntegrityError:
A ...     print("duplicate")
A ... 
A duplicate
A >>> try:
A ...     with transaction.atomic():
A ...         User.objects.create(email='us...@example.com')
A ... except IntegrityError:
A ...     print("duplicate")
A ... 
A <User: us...@example.com>

B                 >>> User.objects.filter(email__startswith='user')
B                 [<User: us...@example.com>, <User: us...@example.com>]

A >>> transaction.commit()

B                 >>> User.objects.filter(email__startswith='user')
B                 [<User: us...@example.com>, <User: us...@example.com>, <User: 
us...@example.com>, <User: us...@example.com>]

A >>> User.objects.create(email='us...@example.com')
A <User: us...@example.com>
A >>> User.objects.create(email='us...@example.com')
A <User: us...@example.com>

B                 >>> User.objects.filter(email__startswith='user')
B                 [<User: us...@example.com>, <User: us...@example.com>, <User: 
us...@example.com>, <User: us...@example.com>]

A >>> transaction.rollback()

B                 >>> User.objects.filter(email__startswith='user')
B                 [<User: us...@example.com>, <User: us...@example.com>, <User: 
us...@example.com>, <User: us...@example.com>]



Best regards, 

-- 
Aymeric.

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/EC2ECCC9-02EB-408D-B6B1-E21BB029DB22%40polytechnique.org.
For more options, visit https://groups.google.com/d/optout.

Reply via email to