On Tue, Mar 5, 2013 at 6:38 AM, Shai Berger <s...@platonix.com> wrote:
> I'm not sure what you mean by "unsafe".
>
> Version 1: code in the question
>
>         rows = MyModel.objects.all()
>         for row in rows:
>             try:
>                 MyModel.objects.get(photo_id=row.photo_id)
>             except:
>                 row.delete()
>
> When Django checks for a second record on get() it reads the second record, so
> a repeatable-read transaction would acquire an exclusive read lock on it. This
> makes it impossible for another transaction to delete the second row before
> the first finishes.

SELECT statements without "FOR UPDATE" do not generally acquire
exclusive locks to my knowledge, even under serializable or repeatable
read isolation levels.  That would be a major issue for parallel
requests if they did.  Also, I don't think there's any distinction
between exclusive read or exclusive write locks; row-level locks are
either just exclusive or shared.

I just verified that, at least in PostgreSQL, the logic above works in
the serializable isolation level but can result in data loss in the
repeatable read isolation level.  I don't have MySQL handy to test,
and Oracle and sqlite3 support serializable and read committed but not
repeatable read. I started two transactions in separate psql shells.
The starting data was:

 id | photo_id
----+----------
  1 |        1
  2 |        1

The first transaction ran:

BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM TEST WHERE photo_id = 1;
DELETE FROM TEST WHERE id = 1;
COMMIT;

The second transaction ran the same thing but deleted id 2 instead.
The individual statements were interleaved (so both queries returned
two rows in the SELECT before either query deleted anything).  The end
result was that both rows were deleted.  Trying the same thing in the
serializable isolation level results in this error when attempting to
commit the second transaction:

ERROR:  could not serialize access due to read/write dependencies
among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during
commit attempt.

> Version 2: code in the first answer.
>
>         for row in MyModel.objects.all():
>             if MyModel.objects.filter(photo_id=row.photo_id).count() > 1:
>                 row.delete()
>
> To the best of my understanding, a repeatable-read transaction gets a read
> lock on all records participating in a count, so again, nobody can delete the
> other records before the transaction finishes.

But with any kind of autocommit on, the transaction ends after the
row.delete(), unlocking the remaining rows and allowing them to then
be deleted.

-- 
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 django-developers+unsubscr...@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at http://groups.google.com/group/django-developers?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to