Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: How can I protect against concurrent modification of database entries

If there a way to protect against concurrent modifications of the same data base entry by two or more users?

It would be acceptable to show an error message to the user performing the second commit/save operation, but data should not be silently overwritten.

I think locking the entry is not an option, as a user might use the "Back" button or simply close his browser, leaving the lock for ever.

like image 230
Ber Avatar asked Nov 26 '08 09:11

Ber


2 Answers

This is how I do optimistic locking in Django:

updated = Entry.objects.filter(Q(id=e.id) && Q(version=e.version))\           .update(updated_field=new_value, version=e.version+1) if not updated:     raise ConcurrentModificationException() 

The code listed above can be implemented as a method in Custom Manager.

I am making the following assumptions:

  • filter().update() will result in a single database query because filter is lazy
  • a database query is atomic

These assumptions are enough to ensure that no one else has updated the entry before. If multiple rows are updated this way you should use transactions.

WARNING Django Doc:

Be aware that the update() method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn't run any save() methods on your models, or emit the pre_save or post_save signals

like image 61
Andrei Savu Avatar answered Oct 13 '22 00:10

Andrei Savu


This question is a bit old and my answer a bit late, but after what I understand this has been fixed in Django 1.4 using:

select_for_update(nowait=True) 

see the docs

Returns a queryset that will lock rows until the end of the transaction, generating a SELECT ... FOR UPDATE SQL statement on supported databases.

Usually, if another transaction has already acquired a lock on one of the selected rows, the query will block until the lock is released. If this is not the behavior you want, call select_for_update(nowait=True). This will make the call non-blocking. If a conflicting lock is already acquired by another transaction, DatabaseError will be raised when the queryset is evaluated.

Of course this will only work if the back-end support the "select for update" feature, which for example sqlite doesn't. Unfortunately: nowait=True is not supported by MySql, there you have to use: nowait=False, which will only block until the lock is released.

like image 42
giZm0 Avatar answered Oct 13 '22 00:10

giZm0