1

I have a situation where I need to update votes for a candidate.

Citizens can vote for this candidate, with more than one vote per candidate. i.e. one person can vote 5 votes, while another person votes 2. In this case this candidate should get 7 votes.

Now, I use Django. And here how the pseudo code looks like

votes = candidate.votes 
vote += citizen.vote 

The problem here, as you can see is a race condition where the candidate’s votes can get overwritten by another citizen’s vote who did a select earlier and set now.

How can avoid this with an ORM like Django?

Quintin Par
  • 15,862
  • 27
  • 93
  • 146
  • I'm sure your database engine can handle concurrent updates, usually with transactions. Check its docs. – DrTyrsa Dec 02 '11 at 07:55
  • And possible duplicate – http://stackoverflow.com/questions/1195858/how-to-deal-with-concurrent-updates-in-databases – DrTyrsa Dec 02 '11 at 08:02

2 Answers2

2

If this is purely an arithmetic expression then Django has a nice API called F expressions


Updating attributes based on existing fields

Sometimes you'll need to perform a simple arithmetic task on a field, such as incrementing or decrementing the current value. The obvious way to achieve this is to do something like:

>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold += 1
>>> product.save()

If the old number_sold value retrieved from the database was 10, then the value of 11 will be written back to the database.

This can be optimized slightly by expressing the update relative to the original field value, rather than as an explicit assignment of a new value. Django provides F() expressions as a way of performing this kind of relative update. Using F() expressions, the previous example would be expressed as:

>>> from django.db.models import F
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold = F('number_sold') + 1
>>> product.save()

This approach doesn't use the initial value from the database. Instead, it makes the database do the update based on whatever value is current at the time that the save() is executed.

Once the object has been saved, you must reload the object in order to access the actual value that was applied to the updated field:

>>> product = Products.objects.get(pk=product.pk)
>>> print product.number_sold
42
Cherian
  • 19,107
  • 12
  • 55
  • 69
  • There is a potential bug in F expression that needs to be handled too http://www.voidspace.org.uk/python/weblog/arch_d7_2011_04_30.shtml – Cherian Dec 02 '11 at 16:51
0

Perhaps the select_for_update QuerySet method is helpful for you.

An excerpt from the docs:

All matched entries will be locked until the end of the transaction block, meaning that other transactions will be prevented from changing or acquiring locks on them.

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.

Mind that this is only available in the Django development release (i.e. > 1.3).

Community
  • 1
  • 1
Mathieu Dhondt
  • 8,405
  • 5
  • 37
  • 58