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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With