Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional updating in Django without race condition

How can I update a model field based on its current value and avoid a race condition? The update task can be written as:

if (self.x == y):
    self.x = z
    self.save()
else:
    raise Exception()

However, there is a race condition. I came up with the following solution:

from django.db import transaction
with transaction.atomic():
    if (self.x == y):
        self.x = z
        self.save()
    else:
        raise Exception()

But is this safe and is there a better way?

like image 295
Jaakko Luttinen Avatar asked Oct 19 '25 15:10

Jaakko Luttinen


1 Answers

No, that atomic() block won't do anything because you've already fetched the values from the database and into self before you try and run the transaction.

If you can express your conditional in query arguments you could do this safely in a single query using update():

num_matched = MyModel.objects.filter(id=self.id, x=y).update(x=z)
if num_matched != 1:
    raise Exception()

If not, you can use select_for_update():

with transaction.atomic():
    current = MyModel.objects.select_for_update().get(id=self.id)
    if (current.x == y):
        current.x = z
        current.save()
    else:
        raise Exception()

The difference between this and your code above is that here you're explicitly telling the database what row to lock before doing your comparison.

like image 165
Kevin Christopher Henry Avatar answered Oct 21 '25 08:10

Kevin Christopher Henry